diff --git a/patches/server/0047-MC-Technical-Survival-Mode.patch b/patches/server/0047-MC-Technical-Survival-Mode.patch index 638c675f..0825507e 100644 --- a/patches/server/0047-MC-Technical-Survival-Mode.patch +++ b/patches/server/0047-MC-Technical-Survival-Mode.patch @@ -6,7 +6,7 @@ Subject: [PATCH] MC Technical Survival Mode Will automatically overwrite some configuration after startup diff --git a/src/main/java/io/papermc/paper/adventure/ChatProcessor.java b/src/main/java/io/papermc/paper/adventure/ChatProcessor.java -index 6177f91146d2bc9dc1a066a24f84581cec0f53cd..04eab3a1f3cb5332293a95f1b3023ed55ab42237 100644 +index 30a59663c3b30430f8ca202965ebb21d4d7abf4c..af20e05ef5e37d742650626c71e6e091d8419ac0 100644 --- a/src/main/java/io/papermc/paper/adventure/ChatProcessor.java +++ b/src/main/java/io/papermc/paper/adventure/ChatProcessor.java @@ -331,14 +331,14 @@ public final class ChatProcessor { @@ -40,10 +40,10 @@ index 5b446e6ac151f99f64f0c442d0b40b5e251bc4c4..c2eb3e8b019dbc0543a2308d7e88e324 final long timeout; if (type == RegionizedPlayerChunkLoader.REGION_PLAYER_TICKET && delayTimeout > 0) { diff --git a/src/main/java/io/papermc/paper/configuration/PaperConfigurations.java b/src/main/java/io/papermc/paper/configuration/PaperConfigurations.java -index fa1c0aee8c3a4d0868482cf5c703bbfd08e09874..c24148d8486420434922864df6f1ffa0b9929612 100644 +index 83a726bcf8b7dce73a361b0d79dbd63a0afc7a12..409ace040a39e5d8a23952c2ac9c39922c69581e 100644 --- a/src/main/java/io/papermc/paper/configuration/PaperConfigurations.java +++ b/src/main/java/io/papermc/paper/configuration/PaperConfigurations.java -@@ -317,6 +317,7 @@ public class PaperConfigurations extends Configurations 0 && ++this.level().spigotConfig.currentPrimedTnt > this.level().spigotConfig.maxTntTicksPerTick) { return; } // Spigot + if (this.level().spigotConfig.maxTntTicksPerTick > 0 && ++this.level().spigotConfig.currentPrimedTnt > (top.leavesmc.leaves.LeavesConfig.mcTechnicalMode ? 2000 : this.level().spigotConfig.maxTntTicksPerTick)) { return; } // Spigot // Leaves - mc technical survival mode - if (!this.isNoGravity()) { - this.setDeltaMovement(this.getDeltaMovement().add(0.0D, -0.04D, 0.0D)); - } + this.applyGravity(); + this.move(MoverType.SELF, this.getDeltaMovement()); + // Paper start - Configurable TNT height nerf diff --git a/src/main/java/net/minecraft/world/entity/projectile/Projectile.java b/src/main/java/net/minecraft/world/entity/projectile/Projectile.java -index 156809090f1f83ad68e7e2477a3cfddac5757a8e..7f191b04f7c84f3188b96c108616424bf54170cb 100644 +index 74c596264d4da551437bd2a23e1c70022cfc73fc..c677be27b5939b283059b83752331ab2819e460b 100644 --- a/src/main/java/net/minecraft/world/entity/projectile/Projectile.java +++ b/src/main/java/net/minecraft/world/entity/projectile/Projectile.java -@@ -119,7 +119,7 @@ public abstract class Projectile extends Entity implements TraceableEntity { +@@ -120,7 +120,7 @@ public abstract class Projectile extends Entity implements TraceableEntity { if (nbt.hasUUID("Owner")) { this.ownerUUID = nbt.getUUID("Owner"); this.cachedOwner = null; @@ -117,10 +117,10 @@ index 156809090f1f83ad68e7e2477a3cfddac5757a8e..7f191b04f7c84f3188b96c108616424b this.leftOwner = nbt.getBoolean("LeftOwner"); diff --git a/src/main/java/net/minecraft/world/level/NaturalSpawner.java b/src/main/java/net/minecraft/world/level/NaturalSpawner.java -index 93fbf20c6e5ded31570415c30b837f5dc0dd5d42..667a480b7c546bb130bbd32a8ae7ce85d37a6b0a 100644 +index 616dc6f7f9cf37c109575d3d33c490054f575592..50879a522a1c9fa817ca226792793c1c3c3e1940 100644 --- a/src/main/java/net/minecraft/world/level/NaturalSpawner.java +++ b/src/main/java/net/minecraft/world/level/NaturalSpawner.java -@@ -95,7 +95,7 @@ public final class NaturalSpawner { +@@ -90,7 +90,7 @@ public final class NaturalSpawner { if (enumcreaturetype != MobCategory.MISC) { // Paper start - Only count natural spawns @@ -130,10 +130,10 @@ index 93fbf20c6e5ded31570415c30b837f5dc0dd5d42..667a480b7c546bb130bbd32a8ae7ce85 entity.spawnReason == org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason.CHUNK_GEN)) { continue; diff --git a/src/main/java/net/minecraft/world/level/block/entity/HopperBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/HopperBlockEntity.java -index 21a64f3a704fb362ed3aade559d55c0804ae5023..006e990d57c7ad1081f63402ef85a3ba856fe329 100644 +index e308d9b28af5969d73c65bd7a08241d4d4e9ee95..6461d2c98bdc914c090405ca42b2c88e267001a9 100644 --- a/src/main/java/net/minecraft/world/level/block/entity/HopperBlockEntity.java +++ b/src/main/java/net/minecraft/world/level/block/entity/HopperBlockEntity.java -@@ -292,7 +292,7 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen +@@ -280,7 +280,7 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen origItemStack.setCount(originalItemCount); } } @@ -142,7 +142,7 @@ index 21a64f3a704fb362ed3aade559d55c0804ae5023..006e990d57c7ad1081f63402ef85a3ba hopper.setCooldown(level.spigotConfig.hopperTransfer); } return false; -@@ -333,7 +333,7 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen +@@ -321,7 +321,7 @@ public class HopperBlockEntity extends RandomizableContainerBlockEntity implemen } origItemStack.setCount(originalItemCount); @@ -151,36 +151,3 @@ index 21a64f3a704fb362ed3aade559d55c0804ae5023..006e990d57c7ad1081f63402ef85a3ba cooldownHopper(hopper); } -diff --git a/src/main/java/top/leavesmc/leaves/util/McTechnicalModeHelper.java b/src/main/java/top/leavesmc/leaves/util/McTechnicalModeHelper.java -new file mode 100644 -index 0000000000000000000000000000000000000000..4822a3806c55126e219364c3f76d0159a6f6453e ---- /dev/null -+++ b/src/main/java/top/leavesmc/leaves/util/McTechnicalModeHelper.java -@@ -0,0 +1,27 @@ -+package top.leavesmc.leaves.util; -+ -+import io.papermc.paper.configuration.GlobalConfiguration; -+import top.leavesmc.leaves.LeavesConfig; -+ -+import java.util.Map; -+ -+public class McTechnicalModeHelper { -+ -+ public static void doMcTechnicalModeIf() { -+ if (LeavesConfig.mcTechnicalMode) { -+ doMcTechnicalMode(); -+ } -+ } -+ -+ public static void doMcTechnicalMode() { -+ GlobalConfiguration.get().unsupportedSettings.allowPistonDuplication = true; -+ GlobalConfiguration.get().unsupportedSettings.allowHeadlessPistons = true; -+ GlobalConfiguration.get().unsupportedSettings.allowPermanentBlockBreakExploits = true; -+ GlobalConfiguration.get().packetLimiter.allPackets = new GlobalConfiguration.PacketLimiter.PacketLimit(GlobalConfiguration.get().packetLimiter.allPackets.interval(), -+ 5000.0, GlobalConfiguration.get().packetLimiter.allPackets.action()); -+ GlobalConfiguration.get().packetLimiter.overrides = Map.of(); -+ GlobalConfiguration.get().unsupportedSettings.allowGrindstoneOverstacking = true; -+ GlobalConfiguration.get().itemValidation.resolveSelectorsInBooks = true; -+ GlobalConfiguration.get().scoreboards.saveEmptyScoreboardTeams = true; -+ } -+} diff --git a/patches/server/0048-Return-nether-portal-fix.patch b/patches/server/0048-Return-nether-portal-fix.patch index 1fcb1251..c20974f5 100644 --- a/patches/server/0048-Return-nether-portal-fix.patch +++ b/patches/server/0048-Return-nether-portal-fix.patch @@ -6,18 +6,18 @@ Subject: [PATCH] Return nether portal fix This patch is powered by NetherPortalFix(https://github.com/TwelveIterationMods/NetherPortalFix) diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index 362ec0d4de4f77511651b28d47b8bdac626f935b..6e084edb04d5bbcd5b25ccd820b3950ea89408d4 100644 +index 66b4bc82c53ce227066625fdc49614bb254dc7f4..7995a286c5d38a90cb596bc9da937a0bcb388004 100644 --- a/src/main/java/net/minecraft/server/level/ServerPlayer.java +++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -187,6 +187,7 @@ import org.bukkit.event.player.PlayerTeleportEvent.TeleportCause; - import org.bukkit.event.player.PlayerToggleSneakEvent; - import org.bukkit.inventory.MainHand; - import top.leavesmc.leaves.bot.ServerBot; +@@ -19,6 +19,7 @@ import java.util.UUID; + import java.util.stream.Collectors; + import javax.annotation.Nullable; + import net.minecraft.BlockUtil; +import top.leavesmc.leaves.util.ReturnPortalManager; - // CraftBukkit end - - public class ServerPlayer extends Player { -@@ -1293,6 +1294,24 @@ public class ServerPlayer extends Player { + import net.minecraft.ChatFormatting; + import net.minecraft.CrashReport; + import net.minecraft.CrashReportCategory; +@@ -1358,6 +1359,24 @@ public class ServerPlayer extends Player { PlayerChangedWorldEvent changeEvent = new PlayerChangedWorldEvent(this.getBukkitEntity(), worldserver1.getWorld()); this.level().getCraftServer().getPluginManager().callEvent(changeEvent); // CraftBukkit end @@ -42,7 +42,7 @@ index 362ec0d4de4f77511651b28d47b8bdac626f935b..6e084edb04d5bbcd5b25ccd820b3950e } // Paper start - Reset shield blocking on dimension change if (this.isBlocking()) { -@@ -1345,6 +1364,30 @@ public class ServerPlayer extends Player { +@@ -1410,6 +1429,30 @@ public class ServerPlayer extends Player { protected Optional getExitPortal(ServerLevel worldserver, BlockPos blockposition, boolean flag, WorldBorder worldborder, int searchRadius, boolean canCreatePortal, int createRadius) { // CraftBukkit Optional optional = super.getExitPortal(worldserver, blockposition, flag, worldborder, searchRadius, canCreatePortal, createRadius); // CraftBukkit @@ -74,18 +74,18 @@ index 362ec0d4de4f77511651b28d47b8bdac626f935b..6e084edb04d5bbcd5b25ccd820b3950e return optional; } else { diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index 82751434c31fe8825f32921f498fd0156da15454..03c278836208a65b77030b629f2b7678ded29b03 100644 +index 823eeb965a32f75415cb6b0a406f7f4f6e3b6871..7d99a70c1eb3aecb2da01557d7c132c15d417fee 100644 --- a/src/main/java/net/minecraft/server/players/PlayerList.java +++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -122,6 +122,7 @@ import org.bukkit.event.player.PlayerSpawnChangeEvent; - // CraftBukkit end - - import top.leavesmc.leaves.bot.ServerBot; +@@ -26,6 +26,7 @@ import java.util.function.Predicate; + import javax.annotation.Nullable; + import net.minecraft.ChatFormatting; + import net.minecraft.FileUtil; +import top.leavesmc.leaves.util.ReturnPortalManager; // Leaves - return portal fix - - public abstract class PlayerList { - -@@ -1004,6 +1005,24 @@ public abstract class PlayerList { + import net.minecraft.commands.CommandSourceStack; + import net.minecraft.core.BlockPos; + import net.minecraft.core.LayeredRegistryAccess; +@@ -983,6 +984,24 @@ public abstract class PlayerList { if (fromWorld != location.getWorld()) { PlayerChangedWorldEvent event = new PlayerChangedWorldEvent(entityplayer.getBukkitEntity(), fromWorld); this.server.server.getPluginManager().callEvent(event); @@ -111,10 +111,10 @@ index 82751434c31fe8825f32921f498fd0156da15454..03c278836208a65b77030b629f2b7678 // Save player file again if they were disconnected diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index 15c8e9214b7df1cf1e9252a17b7acfc080e547a3..ffa9ada2e803bc77f8cae4be81ac4b5c2eee0b7f 100644 +index 40b1990cde0cacbf637039fa1f9e50f31d0428a8..c27d3caaf53d8d745fa5d054f57c9de50e60e2de 100644 --- a/src/main/java/net/minecraft/world/entity/LivingEntity.java +++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -241,7 +241,7 @@ public abstract class LivingEntity extends Entity implements Attackable { +@@ -251,7 +251,7 @@ public abstract class LivingEntity extends Entity implements Attackable { protected ItemStack useItem; public int useItemRemaining; protected int fallFlyTicks; @@ -123,114 +123,3 @@ index 15c8e9214b7df1cf1e9252a17b7acfc080e547a3..ffa9ada2e803bc77f8cae4be81ac4b5c private Optional lastClimbablePos; @Nullable private DamageSource lastDamageSource; -diff --git a/src/main/java/top/leavesmc/leaves/util/ReturnPortalManager.java b/src/main/java/top/leavesmc/leaves/util/ReturnPortalManager.java -new file mode 100644 -index 0000000000000000000000000000000000000000..24f887cbe608eea3beb7dc69cabdca7f32a66a9d ---- /dev/null -+++ b/src/main/java/top/leavesmc/leaves/util/ReturnPortalManager.java -@@ -0,0 +1,105 @@ -+package top.leavesmc.leaves.util; -+ -+import net.minecraft.BlockUtil; -+import net.minecraft.core.BlockPos; -+import net.minecraft.core.registries.Registries; -+import net.minecraft.nbt.CompoundTag; -+import net.minecraft.nbt.ListTag; -+import net.minecraft.nbt.Tag; -+import net.minecraft.resources.ResourceKey; -+import net.minecraft.resources.ResourceLocation; -+import net.minecraft.server.MinecraftServer; -+import net.minecraft.server.level.ServerLevel; -+import net.minecraft.server.level.ServerPlayer; -+import net.minecraft.world.entity.player.Player; -+import net.minecraft.world.level.Level; -+import net.minecraft.world.level.portal.PortalForcer; -+import org.jetbrains.annotations.Nullable; -+ -+import java.util.UUID; -+ -+// Powered by NetherPortalFix(https://github.com/TwelveIterationMods/NetherPortalFix) -+public class ReturnPortalManager { -+ -+ private static final int MAX_PORTAL_DISTANCE_SQ = 16; -+ private static final String RETURN_PORTAL_LIST = "ReturnPortalList"; -+ private static final String RETURN_PORTAL_UID = "UID"; -+ private static final String FROM_DIM = "FromDim"; -+ private static final String FROM_POS = "FromPos"; -+ private static final String TO_MIN_CORNER = "ToMinCorner"; -+ private static final String TO_AXIS_1_SIZE = "ToAxis1Size"; -+ private static final String TO_AXIS_2_SIZE = "ToAxis2Size"; -+ -+ public static BlockUtil.FoundRectangle findPortalAt(Player player, ResourceKey dim, BlockPos pos) { -+ MinecraftServer server = player.level().getServer(); -+ if (server != null) { -+ ServerLevel fromWorld = server.getLevel(dim); -+ if (fromWorld != null) { -+ PortalForcer portalForcer = fromWorld.getPortalForcer(); -+ return portalForcer.findPortalAround(pos, false, fromWorld.getWorldBorder()).orElse(null); -+ } -+ } -+ -+ return null; -+ } -+ -+ public static ListTag getPlayerPortalList(Player player) { -+ CompoundTag data = player.getLeavesData(); -+ ListTag list = data.getList(RETURN_PORTAL_LIST, Tag.TAG_COMPOUND); -+ data.put(RETURN_PORTAL_LIST, list); -+ return list; -+ } -+ -+ @Nullable -+ public static ReturnPortal findReturnPortal(ServerPlayer player, ResourceKey fromDim, BlockPos fromPos) { -+ ListTag portalList = getPlayerPortalList(player); -+ for (Tag entry : portalList) { -+ CompoundTag portal = (CompoundTag) entry; -+ ResourceKey entryFromDim = ResourceKey.create(Registries.DIMENSION, new ResourceLocation(portal.getString(FROM_DIM))); -+ if (entryFromDim == fromDim) { -+ BlockPos portalTrigger = BlockPos.of(portal.getLong(FROM_POS)); -+ if (portalTrigger.distSqr(fromPos) <= MAX_PORTAL_DISTANCE_SQ) { -+ UUID uid = portal.hasUUID(RETURN_PORTAL_UID) ? portal.getUUID(RETURN_PORTAL_UID) : UUID.randomUUID(); -+ BlockPos minCorner = BlockPos.of(portal.getLong(TO_MIN_CORNER)); -+ int axis1Size = portal.getInt(TO_AXIS_1_SIZE); -+ int axis2Size = portal.getInt(TO_AXIS_2_SIZE); -+ return new ReturnPortal(uid, new BlockUtil.FoundRectangle(minCorner, axis1Size, axis2Size)); -+ } -+ } -+ } -+ -+ return null; -+ } -+ -+ public static void storeReturnPortal(ServerPlayer player, ResourceKey fromDim, BlockPos fromPos, BlockUtil.FoundRectangle toPortal) { -+ ListTag portalList = getPlayerPortalList(player); -+ ReturnPortal returnPortal = findReturnPortal(player, fromDim, fromPos); -+ if (returnPortal != null) { -+ removeReturnPortal(player, returnPortal); -+ } -+ -+ CompoundTag portalCompound = new CompoundTag(); -+ portalCompound.putUUID(RETURN_PORTAL_UID, UUID.randomUUID()); -+ portalCompound.putString(FROM_DIM, String.valueOf(fromDim.location())); -+ portalCompound.putLong(FROM_POS, fromPos.asLong()); -+ portalCompound.putLong(TO_MIN_CORNER, toPortal.minCorner.asLong()); -+ portalCompound.putInt(TO_AXIS_1_SIZE, toPortal.axis1Size); -+ portalCompound.putInt(TO_AXIS_2_SIZE, toPortal.axis2Size); -+ portalList.add(portalCompound); -+ } -+ -+ public static void removeReturnPortal(ServerPlayer player, ReturnPortal portal) { -+ // This doesn't check if it's the right toDim, but it's probably so rare for positions to actually overlap that I don't care -+ ListTag portalList = getPlayerPortalList(player); -+ for (int i = 0; i < portalList.size(); i++) { -+ CompoundTag entry = (CompoundTag) portalList.get(i); -+ if (entry.hasUUID(RETURN_PORTAL_UID) && entry.getUUID(RETURN_PORTAL_UID).equals(portal.uid())) { -+ portalList.remove(i); -+ break; -+ } -+ } -+ } -+ -+ public record ReturnPortal(UUID uid, BlockUtil.FoundRectangle rectangle) { -+ } -+} diff --git a/patches/server/0049-Appleskin-Protocol.patch b/patches/server/0049-Appleskin-Protocol.patch deleted file mode 100644 index dab3484c..00000000 --- a/patches/server/0049-Appleskin-Protocol.patch +++ /dev/null @@ -1,118 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: violetc <58360096+s-yh-china@users.noreply.github.com> -Date: Wed, 25 Jan 2023 11:03:53 +0800 -Subject: [PATCH] Appleskin Protocol - - -diff --git a/src/main/java/top/leavesmc/leaves/protocol/AppleSkinProtocol.java b/src/main/java/top/leavesmc/leaves/protocol/AppleSkinProtocol.java -new file mode 100644 -index 0000000000000000000000000000000000000000..6fd6a1c9ef54c95e452e08a69f9ddb1d6600e651 ---- /dev/null -+++ b/src/main/java/top/leavesmc/leaves/protocol/AppleSkinProtocol.java -@@ -0,0 +1,106 @@ -+package top.leavesmc.leaves.protocol; -+ -+import net.minecraft.resources.ResourceLocation; -+import net.minecraft.server.MinecraftServer; -+import net.minecraft.server.level.ServerPlayer; -+import net.minecraft.world.food.FoodData; -+import org.jetbrains.annotations.Contract; -+import org.jetbrains.annotations.NotNull; -+import top.leavesmc.leaves.LeavesConfig; -+import top.leavesmc.leaves.protocol.core.LeavesProtocol; -+import top.leavesmc.leaves.protocol.core.ProtocolHandler; -+import top.leavesmc.leaves.protocol.core.ProtocolUtils; -+ -+import java.util.HashMap; -+import java.util.HashSet; -+import java.util.Map; -+import java.util.Set; -+import java.util.UUID; -+ -+@LeavesProtocol(namespace = "appleskin") -+public class AppleSkinProtocol { -+ -+ public static final String PROTOCOL_ID = "appleskin"; -+ -+ private static final ResourceLocation SATURATION_KEY = id("saturation_sync"); -+ private static final ResourceLocation EXHAUSTION_KEY = id("exhaustion_sync"); -+ -+ private static final Map previousSaturationLevels = new HashMap<>(); -+ private static final Map previousExhaustionLevels = new HashMap<>(); -+ -+ private static final float MINIMUM_EXHAUSTION_CHANGE_THRESHOLD = 0.01F; -+ -+ private static final Set players = new HashSet<>(); -+ -+ @Contract("_ -> new") -+ public static @NotNull ResourceLocation id(String path) { -+ return new ResourceLocation(PROTOCOL_ID, path); -+ } -+ -+ @ProtocolHandler.PlayerJoin -+ public static void onPlayerLoggedIn(@NotNull ServerPlayer player) { -+ if (LeavesConfig.appleskinProtocol) { -+ resetPlayerData(player); -+ } -+ } -+ -+ @ProtocolHandler.PlayerLeave -+ public static void onPlayerLoggedOut(@NotNull ServerPlayer player) { -+ if (LeavesConfig.appleskinProtocol) { -+ players.remove(player); -+ resetPlayerData(player); -+ } -+ } -+ -+ @ProtocolHandler.MinecraftRegister(ignoreId = true) -+ public static void onPlayerSubscribed(@NotNull ServerPlayer player) { -+ if (LeavesConfig.appleskinProtocol) { -+ players.add(player); -+ } -+ } -+ -+ @ProtocolHandler.Ticker -+ public static void tick() { -+ if (LeavesConfig.appleskinProtocol) { -+ for (ServerPlayer player : players) { -+ FoodData data = player.getFoodData(); -+ -+ float saturation = data.getSaturationLevel(); -+ Float previousSaturation = previousSaturationLevels.get(player.getUUID()); -+ if (previousSaturation == null || saturation != previousSaturation) { -+ ProtocolUtils.sendPayloadPacket(player, SATURATION_KEY, buf -> { -+ buf.writeFloat(saturation); -+ }); -+ previousSaturationLevels.put(player.getUUID(), saturation); -+ } -+ -+ float exhaustion = data.getExhaustionLevel(); -+ Float previousExhaustion = previousExhaustionLevels.get(player.getUUID()); -+ if (previousExhaustion == null || Math.abs(exhaustion - previousExhaustion) >= MINIMUM_EXHAUSTION_CHANGE_THRESHOLD) { -+ ProtocolUtils.sendPayloadPacket(player, EXHAUSTION_KEY, buf -> { -+ buf.writeFloat(exhaustion); -+ }); -+ previousExhaustionLevels.put(player.getUUID(), exhaustion); -+ } -+ } -+ } -+ } -+ -+ @ProtocolHandler.ReloadServer -+ public static void onServerReload() { -+ if (!LeavesConfig.appleskinProtocol) { -+ disableAllPlayer(); -+ } -+ } -+ -+ public static void disableAllPlayer() { -+ for (ServerPlayer player : MinecraftServer.getServer().getPlayerList().getPlayers()) { -+ onPlayerLoggedOut(player); -+ } -+ } -+ -+ private static void resetPlayerData(@NotNull ServerPlayer player) { -+ previousExhaustionLevels.remove(player.getUUID()); -+ previousSaturationLevels.remove(player.getUUID()); -+ } -+} diff --git a/patches/server/0049-Placeholder-for-Appleskin-Protocol.patch b/patches/server/0049-Placeholder-for-Appleskin-Protocol.patch new file mode 100644 index 00000000..f06f0496 --- /dev/null +++ b/patches/server/0049-Placeholder-for-Appleskin-Protocol.patch @@ -0,0 +1,15 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: MC_XiaoHei +Date: Wed, 8 May 2024 22:12:13 +0800 +Subject: [PATCH] Placeholder for Appleskin-Protocol + + +diff --git a/.gitignore b/.gitignore +index 3811c0d849a3eb028ed1a6b7a2d4747f7f570448..1ad93453221244f880855b510e888e759275b640 100644 +--- a/.gitignore ++++ b/.gitignore +@@ -46,3 +46,4 @@ dependency-reduced-pom.xml + # vs code + /.vscode + /.factorypath ++ diff --git a/patches/server/0050-Placeholder-for-Xaero-Map-Protocol.patch b/patches/server/0050-Placeholder-for-Xaero-Map-Protocol.patch new file mode 100644 index 00000000..1a67e589 --- /dev/null +++ b/patches/server/0050-Placeholder-for-Xaero-Map-Protocol.patch @@ -0,0 +1,15 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: MC_XiaoHei +Date: Wed, 8 May 2024 22:14:22 +0800 +Subject: [PATCH] Placeholder for Xaero-Map-Protocol + + +diff --git a/.gitignore b/.gitignore +index 1ad93453221244f880855b510e888e759275b640..3811c0d849a3eb028ed1a6b7a2d4747f7f570448 100644 +--- a/.gitignore ++++ b/.gitignore +@@ -46,4 +46,3 @@ dependency-reduced-pom.xml + # vs code + /.vscode + /.factorypath +- diff --git a/patches/server/0050-Xaero-Map-Protocol.patch b/patches/server/0050-Xaero-Map-Protocol.patch deleted file mode 100644 index 38ba4ab7..00000000 --- a/patches/server/0050-Xaero-Map-Protocol.patch +++ /dev/null @@ -1,66 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: violetc <58360096+s-yh-china@users.noreply.github.com> -Date: Fri, 27 Jan 2023 09:42:57 +0800 -Subject: [PATCH] Xaero Map Protocol - - -diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index 03c278836208a65b77030b629f2b7678ded29b03..6e5a7edfbc8781def735d0b2250888288560fbc4 100644 ---- a/src/main/java/net/minecraft/server/players/PlayerList.java -+++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -1323,6 +1323,7 @@ public abstract class PlayerList { - player.connection.send(new ClientboundInitializeBorderPacket(worldborder)); - player.connection.send(new ClientboundSetTimePacket(world.getGameTime(), world.getDayTime(), world.getGameRules().getBoolean(GameRules.RULE_DAYLIGHT))); - player.connection.send(new ClientboundSetDefaultSpawnPositionPacket(world.getSharedSpawnPos(), world.getSharedSpawnAngle())); -+ top.leavesmc.leaves.protocol.XaeroMapProtocol.onSendWorldInfo(player); // Leaves - xaero map protocol - if (world.isRaining()) { - // CraftBukkit start - handle player weather - // entityplayer.connection.send(new PacketPlayOutGameStateChange(PacketPlayOutGameStateChange.START_RAINING, 0.0F)); -diff --git a/src/main/java/top/leavesmc/leaves/protocol/XaeroMapProtocol.java b/src/main/java/top/leavesmc/leaves/protocol/XaeroMapProtocol.java -new file mode 100644 -index 0000000000000000000000000000000000000000..c0317bcaee60e8680812119bf94e1032d7816023 ---- /dev/null -+++ b/src/main/java/top/leavesmc/leaves/protocol/XaeroMapProtocol.java -@@ -0,0 +1,42 @@ -+package top.leavesmc.leaves.protocol; -+ -+import net.minecraft.resources.ResourceLocation; -+import net.minecraft.server.level.ServerPlayer; -+import org.jetbrains.annotations.Contract; -+import org.jetbrains.annotations.NotNull; -+import top.leavesmc.leaves.LeavesConfig; -+import top.leavesmc.leaves.protocol.core.LeavesProtocol; -+import top.leavesmc.leaves.protocol.core.ProtocolUtils; -+ -+@LeavesProtocol(namespace = {"xaerominimap", "xaeroworldmap"}) -+public class XaeroMapProtocol { -+ -+ public static final String PROTOCOL_ID_MINI = "xaerominimap"; -+ public static final String PROTOCOL_ID_WORLD = "xaeroworldmap"; -+ -+ private static final ResourceLocation MINIMAP_KEY = idMini("main"); -+ private static final ResourceLocation WORLDMAP_KEY = idWorld("main"); -+ -+ @Contract("_ -> new") -+ public static @NotNull ResourceLocation idMini(String path) { -+ return new ResourceLocation(PROTOCOL_ID_MINI, path); -+ } -+ -+ @Contract("_ -> new") -+ public static @NotNull ResourceLocation idWorld(String path) { -+ return new ResourceLocation(PROTOCOL_ID_WORLD, path); -+ } -+ -+ public static void onSendWorldInfo(@NotNull ServerPlayer player) { -+ if (LeavesConfig.xaeroMapProtocol) { -+ ProtocolUtils.sendPayloadPacket(player, MINIMAP_KEY, buf -> { -+ buf.writeByte(0); -+ buf.writeInt(LeavesConfig.xaeroMapServerID); -+ }); -+ ProtocolUtils.sendPayloadPacket(player, WORLDMAP_KEY, buf -> { -+ buf.writeByte(0); -+ buf.writeInt(LeavesConfig.xaeroMapServerID); -+ }); -+ } -+ } -+} diff --git a/patches/server/0051-Placeholder-for-Leaves-Extra-Yggdrasil-Service.patch b/patches/server/0051-Placeholder-for-Leaves-Extra-Yggdrasil-Service.patch new file mode 100644 index 00000000..ec365351 --- /dev/null +++ b/patches/server/0051-Placeholder-for-Leaves-Extra-Yggdrasil-Service.patch @@ -0,0 +1,15 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: MC_XiaoHei +Date: Wed, 8 May 2024 22:15:03 +0800 +Subject: [PATCH] Placeholder for Leaves-Extra-Yggdrasil-Service + + +diff --git a/.gitignore b/.gitignore +index 3811c0d849a3eb028ed1a6b7a2d4747f7f570448..1ad93453221244f880855b510e888e759275b640 100644 +--- a/.gitignore ++++ b/.gitignore +@@ -46,3 +46,4 @@ dependency-reduced-pom.xml + # vs code + /.vscode + /.factorypath ++ diff --git a/patches/server/0052-Use-vanilla-random-config.patch b/patches/server/0052-Use-vanilla-random-config.patch index 0916a2d4..7aa66358 100644 --- a/patches/server/0052-Use-vanilla-random-config.patch +++ b/patches/server/0052-Use-vanilla-random-config.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Use vanilla random config diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index f491c1d402499e68191d4cdc9c8ef2e7caae9c7b..017efdee91fe6326c8e0270c294276b24b42604a 100644 +index 29201c95d95d7e3af5a51f306a3aa625d53c4c02..575252c43504addaf1561fdfd3152ec8f802fb08 100644 --- a/src/main/java/net/minecraft/world/entity/Entity.java +++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -578,7 +578,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S +@@ -580,7 +580,7 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess this.bb = Entity.INITIAL_AABB; this.stuckSpeedMultiplier = Vec3.ZERO; this.nextStep = 1.0F; @@ -18,10 +18,10 @@ index f491c1d402499e68191d4cdc9c8ef2e7caae9c7b..017efdee91fe6326c8e0270c294276b2 this.fluidHeight = new Object2DoubleArrayMap(2); this.fluidOnEyes = new HashSet(); diff --git a/src/main/java/net/minecraft/world/entity/animal/Bee.java b/src/main/java/net/minecraft/world/entity/animal/Bee.java -index f9521a6e115f0c975a7885b024c99eae300b63bf..358b4efefbf9de576194040ca5da9b877afa3e1b 100644 +index 0dfb8109fd8c022b079da00f6a0e3fc85b57bf7a..1aba01e2ddcfe87ea3b3ca0172db0f6c2e774da3 100644 --- a/src/main/java/net/minecraft/world/entity/animal/Bee.java +++ b/src/main/java/net/minecraft/world/entity/animal/Bee.java -@@ -1036,7 +1036,7 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal { +@@ -1018,7 +1018,7 @@ public class Bee extends Animal implements NeutralMob, FlyingAnimal { BeeGoToHiveGoal() { super(); @@ -31,10 +31,10 @@ index f9521a6e115f0c975a7885b024c99eae300b63bf..358b4efefbf9de576194040ca5da9b87 this.setFlags(EnumSet.of(Goal.Flag.MOVE)); } diff --git a/src/main/java/net/minecraft/world/entity/animal/Squid.java b/src/main/java/net/minecraft/world/entity/animal/Squid.java -index 4f32597c7af34d599f6658fe4962d41624e60419..23efed794def54b0563ed5ab1a43773f3bb7378b 100644 +index 43b4ea96c5c4a6234e5b83d41db9b85c1fe27b8f..e900af578b12753f06122cebc4c6f73ccbc284ba 100644 --- a/src/main/java/net/minecraft/world/entity/animal/Squid.java +++ b/src/main/java/net/minecraft/world/entity/animal/Squid.java -@@ -44,7 +44,7 @@ public class Squid extends WaterAnimal { +@@ -42,7 +42,7 @@ public class Squid extends WaterAnimal { public Squid(EntityType type, Level world) { super(type, world); @@ -44,10 +44,10 @@ index 4f32597c7af34d599f6658fe4962d41624e60419..23efed794def54b0563ed5ab1a43773f } diff --git a/src/main/java/net/minecraft/world/entity/item/ItemEntity.java b/src/main/java/net/minecraft/world/entity/item/ItemEntity.java -index b5b522c1307a58453aa293aaa808dd9ec86901fc..45dc79f877bbb4a5384c21a32dc009270b8dc021 100644 +index b1708271baad4309e5b10bab1194b2a4f7a1e105..617b0a42951512256fd68a23b1021450b8066b54 100644 --- a/src/main/java/net/minecraft/world/entity/item/ItemEntity.java +++ b/src/main/java/net/minecraft/world/entity/item/ItemEntity.java -@@ -74,7 +74,13 @@ public class ItemEntity extends Entity implements TraceableEntity { +@@ -78,7 +78,13 @@ public class ItemEntity extends Entity implements TraceableEntity { // Paper start - Don't use level random in entity constructors (to make them thread-safe) this(EntityType.ITEM, world); this.setPos(x, y, z); @@ -63,10 +63,10 @@ index b5b522c1307a58453aa293aaa808dd9ec86901fc..45dc79f877bbb4a5384c21a32dc00927 // Paper end - Don't use level random in entity constructors // Leaves start - stackable shulker boxes diff --git a/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java b/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java -index 89681445d9d66a7fdbab50c39fd598fb45cd7441..6c6c953b2ea5b461ec8ed9a8adf0d28c78ea21d9 100644 +index 7482121be813cf1f15be459040ea0a4839bfc26e..80f3878d2ef2046453d21b0293188760f6f14f5d 100644 --- a/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java +++ b/src/main/java/net/minecraft/world/entity/item/PrimedTnt.java -@@ -44,7 +44,7 @@ public class PrimedTnt extends Entity implements TraceableEntity { +@@ -42,7 +42,7 @@ public class PrimedTnt extends Entity implements TraceableEntity { public PrimedTnt(Level world, double x, double y, double z, @Nullable LivingEntity igniter) { this(EntityType.TNT, world); this.setPos(x, y, z); diff --git a/patches/server/0053-Fix-update-suppression-crash.patch b/patches/server/0053-Fix-update-suppression-crash.patch index d55494f3..87d9b21c 100644 --- a/patches/server/0053-Fix-update-suppression-crash.patch +++ b/patches/server/0053-Fix-update-suppression-crash.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Fix update suppression crash diff --git a/src/main/java/net/minecraft/network/protocol/PacketUtils.java b/src/main/java/net/minecraft/network/protocol/PacketUtils.java -index 070231080052ef3d0da179a8ed8b754ed6f8dd11..84530927aa4517709ba3f38d27755bb5d2eaac34 100644 +index d6daa27a8d7aca00b181e90d789f4249e8437d29..869df727d1b05d74a6830c790ad4a3ff6a703812 100644 --- a/src/main/java/net/minecraft/network/protocol/PacketUtils.java +++ b/src/main/java/net/minecraft/network/protocol/PacketUtils.java -@@ -51,6 +51,10 @@ public class PacketUtils { +@@ -52,6 +52,10 @@ public class PacketUtils { if (listener.shouldHandleMessage(packet)) { try { packet.handle(listener); @@ -17,13 +17,13 @@ index 070231080052ef3d0da179a8ed8b754ed6f8dd11..84530927aa4517709ba3f38d27755bb5 + top.leavesmc.leaves.LeavesLogger.LOGGER.info(exception.getMessage()); + // Leaves start - update suppression crash fix } catch (Exception exception) { - label25: - { + if (exception instanceof ReportedException) { + ReportedException reportedexception = (ReportedException) exception; diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 4091c7b992d885fa14e03df7fa8c5edc0c25f76d..c5c9a9a6f42da7b71103fd754661534dbc9fc13e 100644 +index e312c93d246b868dc5a9c8f881d1ed8f02adb325..7952507524e08afa669815561c5ff5b8782a0b44 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -1701,7 +1701,13 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop +Date: Wed, 8 May 2024 22:19:25 +0800 +Subject: [PATCH] Placeholder for Bedrock-break-list + + +diff --git a/.gitignore b/.gitignore +index 1ad93453221244f880855b510e888e759275b640..3811c0d849a3eb028ed1a6b7a2d4747f7f570448 100644 +--- a/.gitignore ++++ b/.gitignore +@@ -46,4 +46,3 @@ dependency-reduced-pom.xml + # vs code + /.vscode + /.factorypath +- diff --git a/patches/server/0055-Fix-trapdoor-feature.patch b/patches/server/0055-Fix-trapdoor-feature.patch index ed7f96f2..f3d42a41 100644 --- a/patches/server/0055-Fix-trapdoor-feature.patch +++ b/patches/server/0055-Fix-trapdoor-feature.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Fix trapdoor feature diff --git a/src/main/java/net/minecraft/world/level/block/TrapDoorBlock.java b/src/main/java/net/minecraft/world/level/block/TrapDoorBlock.java -index 1027e5e8da4323714a3e7fed8d39264b8b9e50e7..c0c28e905b2cf8db1aff105dd2055702df670ff9 100644 +index 2b5fdcb12dcd8730df24113b9a6d295f000dd9f6..527a77b96a06098aaa4adac5ed46b45851bba00b 100644 --- a/src/main/java/net/minecraft/world/level/block/TrapDoorBlock.java +++ b/src/main/java/net/minecraft/world/level/block/TrapDoorBlock.java @@ -153,6 +153,8 @@ public class TrapDoorBlock extends HorizontalDirectionalBlock implements SimpleW diff --git a/patches/server/0056-Placeholder-for-Syncmatica-Protocol.patch b/patches/server/0056-Placeholder-for-Syncmatica-Protocol.patch new file mode 100644 index 00000000..fec7798c --- /dev/null +++ b/patches/server/0056-Placeholder-for-Syncmatica-Protocol.patch @@ -0,0 +1,15 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: MC_XiaoHei +Date: Wed, 8 May 2024 22:26:38 +0800 +Subject: [PATCH] Placeholder for Syncmatica-Protocol + + +diff --git a/.gitignore b/.gitignore +index 3811c0d849a3eb028ed1a6b7a2d4747f7f570448..1ad93453221244f880855b510e888e759275b640 100644 +--- a/.gitignore ++++ b/.gitignore +@@ -46,3 +46,4 @@ dependency-reduced-pom.xml + # vs code + /.vscode + /.factorypath ++ diff --git a/patches/server/0056-Syncmatica-Protocol.patch b/patches/server/0056-Syncmatica-Protocol.patch deleted file mode 100644 index 57973103..00000000 --- a/patches/server/0056-Syncmatica-Protocol.patch +++ /dev/null @@ -1,2021 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: violetc <58360096+s-yh-china@users.noreply.github.com> -Date: Thu, 18 May 2023 16:16:56 +0800 -Subject: [PATCH] Syncmatica Protocol - -This patch is Powered by Syncmatica(https://github.com/End-Tech/syncmatica) - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 98b0cee150e9e27e6bafc7c2afc9d5008ca8ef82..2d388dcafc02a04348a1c07d970b7a8bb8fdf014 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -312,6 +312,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - Objects.requireNonNull(server); - this.signedMessageDecoder = SignedMessageChain.Decoder.unsigned(uuid, server::enforceSecureProfile); - this.chatMessageChain = new FutureChain(server.chatExecutor); // CraftBukkit - async chat -+ this.exchangeTarget = new top.leavesmc.leaves.protocol.syncmatica.exchange.ExchangeTarget(this); // Leaves - Syncmatica Protocol - } - - // CraftBukkit start - add fields -@@ -330,6 +331,8 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - private boolean justTeleported = false; - // CraftBukkit end - -+ public final top.leavesmc.leaves.protocol.syncmatica.exchange.ExchangeTarget exchangeTarget; // Leaves - Syncmatica Protocol -+ - @Override - public void tick() { - if (this.ackBlockChangesUpTo > -1) { -diff --git a/src/main/java/top/leavesmc/leaves/protocol/syncmatica/CommunicationManager.java b/src/main/java/top/leavesmc/leaves/protocol/syncmatica/CommunicationManager.java -new file mode 100644 -index 0000000000000000000000000000000000000000..4c2aa74795b0883f280eaa721a83ea6891ffdab2 ---- /dev/null -+++ b/src/main/java/top/leavesmc/leaves/protocol/syncmatica/CommunicationManager.java -@@ -0,0 +1,391 @@ -+package top.leavesmc.leaves.protocol.syncmatica; -+ -+import com.mojang.authlib.GameProfile; -+import io.netty.buffer.Unpooled; -+import net.minecraft.core.BlockPos; -+import net.minecraft.network.FriendlyByteBuf; -+import net.minecraft.network.chat.Component; -+import net.minecraft.network.protocol.common.ServerboundCustomPayloadPacket; -+import net.minecraft.resources.ResourceLocation; -+import net.minecraft.server.level.ServerPlayer; -+import net.minecraft.server.network.ServerGamePacketListenerImpl; -+import net.minecraft.world.level.block.Mirror; -+import net.minecraft.world.level.block.Rotation; -+import org.jetbrains.annotations.NotNull; -+import top.leavesmc.leaves.protocol.core.LeavesProtocol; -+import top.leavesmc.leaves.protocol.core.LeavesProtocolManager; -+import top.leavesmc.leaves.protocol.core.ProtocolHandler; -+import top.leavesmc.leaves.protocol.syncmatica.exchange.DownloadExchange; -+import top.leavesmc.leaves.protocol.syncmatica.exchange.Exchange; -+import top.leavesmc.leaves.protocol.syncmatica.exchange.ExchangeTarget; -+import top.leavesmc.leaves.protocol.syncmatica.exchange.ModifyExchangeServer; -+import top.leavesmc.leaves.protocol.syncmatica.exchange.UploadExchange; -+import top.leavesmc.leaves.protocol.syncmatica.exchange.VersionHandshakeServer; -+ -+import java.io.File; -+import java.io.FileNotFoundException; -+import java.io.IOException; -+import java.security.NoSuchAlgorithmException; -+import java.util.ArrayList; -+import java.util.Collection; -+import java.util.HashMap; -+import java.util.List; -+import java.util.Map; -+import java.util.UUID; -+ -+@LeavesProtocol(namespace = "syncmatica") -+public class CommunicationManager { -+ -+ private static final Map> downloadingFile = new HashMap<>(); -+ private static final Map playerMap = new HashMap<>(); -+ -+ protected static final Collection broadcastTargets = new ArrayList<>(); -+ -+ protected static final Map downloadState = new HashMap<>(); -+ protected static final Map modifyState = new HashMap<>(); -+ -+ protected static final Rotation[] rotOrdinals = Rotation.values(); -+ protected static final Mirror[] mirOrdinals = Mirror.values(); -+ -+ public CommunicationManager() { -+ } -+ -+ public static GameProfile getGameProfile(final ExchangeTarget exchangeTarget) { -+ return playerMap.get(exchangeTarget).getGameProfile(); -+ } -+ -+ public void sendMessage(final @NotNull ExchangeTarget client, final MessageType type, final String identifier) { -+ if (client.getFeatureSet().hasFeature(Feature.MESSAGE)) { -+ final FriendlyByteBuf newPacketBuf = new FriendlyByteBuf(Unpooled.buffer()); -+ newPacketBuf.writeUtf(type.toString()); -+ newPacketBuf.writeUtf(identifier); -+ client.sendPacket(PacketType.MESSAGE.identifier, newPacketBuf); -+ } else if (playerMap.containsKey(client)) { -+ final ServerPlayer player = playerMap.get(client); -+ player.sendSystemMessage(Component.literal("Syncmatica " + type.toString() + " " + identifier)); -+ } -+ } -+ -+ @ProtocolHandler.PlayerJoin -+ public static void onPlayerJoin(ServerPlayer player) { -+ final ExchangeTarget newPlayer = player.connection.exchangeTarget; -+ final VersionHandshakeServer hi = new VersionHandshakeServer(newPlayer); -+ playerMap.put(newPlayer, player); -+ final GameProfile profile = player.getGameProfile(); -+ SyncmaticaProtocol.getPlayerIdentifierProvider().updateName(profile.getId(), profile.getName()); -+ startExchangeUnchecked(hi); -+ } -+ -+ @ProtocolHandler.PlayerLeave -+ public static void onPlayerLeave(ServerPlayer player) { -+ final ExchangeTarget oldPlayer = player.connection.exchangeTarget; -+ final Collection potentialMessageTarget = oldPlayer.getExchanges(); -+ if (potentialMessageTarget != null) { -+ for (final Exchange target : potentialMessageTarget) { -+ target.close(false); -+ handleExchange(target); -+ } -+ } -+ broadcastTargets.remove(oldPlayer); -+ playerMap.remove(oldPlayer); -+ } -+ -+ @ProtocolHandler.PayloadReceiver(payload = LeavesProtocolManager.LeavesPayload.class, ignoreId = true, payloadId = "") -+ public static void onPacketGet(ServerPlayer player, LeavesProtocolManager.LeavesPayload payload) { -+ onPacket(player.connection.exchangeTarget, payload.id(), payload.data()); -+ } -+ -+ public static void onPacket(final @NotNull ExchangeTarget source, final ResourceLocation id, final FriendlyByteBuf packetBuf) { -+ Exchange handler = null; -+ final Collection potentialMessageTarget = source.getExchanges(); -+ if (potentialMessageTarget != null) { -+ for (final Exchange target : potentialMessageTarget) { -+ if (target.checkPacket(id, packetBuf)) { -+ target.handle(id, packetBuf); -+ handler = target; -+ break; -+ } -+ } -+ } -+ if (handler == null) { -+ handle(source, id, packetBuf); -+ } else if (handler.isFinished()) { -+ notifyClose(handler); -+ } -+ } -+ -+ protected static void handle(ExchangeTarget source, @NotNull ResourceLocation id, FriendlyByteBuf packetBuf) { -+ if (id.equals(PacketType.REQUEST_LITEMATIC.identifier)) { -+ final UUID syncmaticaId = packetBuf.readUUID(); -+ final ServerPlacement placement = SyncmaticaProtocol.getSyncmaticManager().getPlacement(syncmaticaId); -+ if (placement == null) { -+ return; -+ } -+ final File toUpload = SyncmaticaProtocol.getFileStorage().getLocalLitematic(placement); -+ final UploadExchange upload; -+ try { -+ upload = new UploadExchange(placement, toUpload, source); -+ } catch (final FileNotFoundException e) { -+ e.printStackTrace(); -+ return; -+ } -+ startExchange(upload); -+ return; -+ } -+ if (id.equals(PacketType.REGISTER_METADATA.identifier)) { -+ final ServerPlacement placement = receiveMetaData(packetBuf, source); -+ if (SyncmaticaProtocol.getSyncmaticManager().getPlacement(placement.getId()) != null) { -+ cancelShare(source, placement); -+ return; -+ } -+ -+ final GameProfile profile = playerMap.get(source).getGameProfile(); -+ final PlayerIdentifier playerIdentifier = SyncmaticaProtocol.getPlayerIdentifierProvider().createOrGet(profile); -+ if (!placement.getOwner().equals(playerIdentifier)) { -+ placement.setOwner(playerIdentifier); -+ placement.setLastModifiedBy(playerIdentifier); -+ } -+ -+ if (!SyncmaticaProtocol.getFileStorage().getLocalState(placement).isLocalFileReady()) { -+ if (SyncmaticaProtocol.getFileStorage().getLocalState(placement) == LocalLitematicState.DOWNLOADING_LITEMATIC) { -+ downloadingFile.computeIfAbsent(placement.getHash(), key -> new ArrayList<>()).add(placement); -+ return; -+ } -+ try { -+ download(placement, source); -+ } catch (final Exception e) { -+ e.printStackTrace(); -+ } -+ return; -+ } -+ -+ addPlacement(source, placement); -+ return; -+ } -+ if (id.equals(PacketType.REMOVE_SYNCMATIC.identifier)) { -+ final UUID placementId = packetBuf.readUUID(); -+ final ServerPlacement placement = SyncmaticaProtocol.getSyncmaticManager().getPlacement(placementId); -+ if (placement != null) { -+ if (!getGameProfile(source).getId().equals(placement.getOwner().uuid)) { -+ return; -+ } -+ -+ final Exchange modifier = getModifier(placement); -+ if (modifier != null) { -+ modifier.close(true); -+ notifyClose(modifier); -+ } -+ SyncmaticaProtocol.getSyncmaticManager().removePlacement(placement); -+ for (final ExchangeTarget client : broadcastTargets) { -+ final FriendlyByteBuf newPacketBuf = new FriendlyByteBuf(Unpooled.buffer()); -+ newPacketBuf.writeUUID(placement.getId()); -+ client.sendPacket(PacketType.REMOVE_SYNCMATIC.identifier, newPacketBuf); -+ } -+ } -+ } -+ if (id.equals(PacketType.MODIFY_REQUEST.identifier)) { -+ final UUID placementId = packetBuf.readUUID(); -+ final ModifyExchangeServer modifier = new ModifyExchangeServer(placementId, source); -+ startExchange(modifier); -+ } -+ } -+ -+ protected static void handleExchange(Exchange exchange) { -+ if (exchange instanceof DownloadExchange) { -+ final ServerPlacement p = ((DownloadExchange) exchange).getPlacement(); -+ -+ if (exchange.isSuccessful()) { -+ addPlacement(exchange.getPartner(), p); -+ if (downloadingFile.containsKey(p.getHash())) { -+ for (final ServerPlacement placement : downloadingFile.get(p.getHash())) { -+ addPlacement(exchange.getPartner(), placement); -+ } -+ } -+ } else { -+ cancelShare(exchange.getPartner(), p); -+ if (downloadingFile.containsKey(p.getHash())) { -+ for (final ServerPlacement placement : downloadingFile.get(p.getHash())) { -+ cancelShare(exchange.getPartner(), placement); -+ } -+ } -+ } -+ -+ downloadingFile.remove(p.getHash()); -+ return; -+ } -+ if (exchange instanceof VersionHandshakeServer && exchange.isSuccessful()) { -+ broadcastTargets.add(exchange.getPartner()); -+ } -+ if (exchange instanceof ModifyExchangeServer && exchange.isSuccessful()) { -+ final ServerPlacement placement = ((ModifyExchangeServer) exchange).getPlacement(); -+ for (final ExchangeTarget client : broadcastTargets) { -+ if (client.getFeatureSet().hasFeature(Feature.MODIFY)) { -+ final FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer()); -+ buf.writeUUID(placement.getId()); -+ putPositionData(placement, buf, client); -+ if (client.getFeatureSet().hasFeature(Feature.CORE_EX)) { -+ buf.writeUUID(placement.getLastModifiedBy().uuid); -+ buf.writeUtf(placement.getLastModifiedBy().getName()); -+ } -+ client.sendPacket(PacketType.MODIFY.identifier, buf); -+ } else { -+ final FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer()); -+ buf.writeUUID(placement.getId()); -+ client.sendPacket(PacketType.REMOVE_SYNCMATIC.identifier, buf); -+ final FriendlyByteBuf buf2 = new FriendlyByteBuf(Unpooled.buffer()); -+ putMetaData(placement, buf2, client); -+ client.sendPacket(PacketType.REGISTER_METADATA.identifier, buf2); -+ } -+ } -+ } -+ } -+ -+ private static void addPlacement(final ExchangeTarget t, final @NotNull ServerPlacement placement) { -+ if (SyncmaticaProtocol.getSyncmaticManager().getPlacement(placement.getId()) != null) { -+ cancelShare(t, placement); -+ return; -+ } -+ SyncmaticaProtocol.getSyncmaticManager().addPlacement(placement); -+ for (final ExchangeTarget target : broadcastTargets) { -+ sendMetaData(placement, target); -+ } -+ } -+ -+ private static void cancelShare(final @NotNull ExchangeTarget source, final @NotNull ServerPlacement placement) { -+ final FriendlyByteBuf FriendlyByteBuf = new FriendlyByteBuf(Unpooled.buffer()); -+ FriendlyByteBuf.writeUUID(placement.getId()); -+ source.sendPacket(PacketType.CANCEL_SHARE.identifier, FriendlyByteBuf); -+ } -+ -+ public static void sendMetaData(final ServerPlacement metaData, final ExchangeTarget target) { -+ final FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer()); -+ putMetaData(metaData, buf, target); -+ target.sendPacket(PacketType.REGISTER_METADATA.identifier, buf); -+ } -+ -+ public static void putMetaData(final @NotNull ServerPlacement metaData, final @NotNull FriendlyByteBuf buf, final @NotNull ExchangeTarget exchangeTarget) { -+ buf.writeUUID(metaData.getId()); -+ -+ buf.writeUtf(SyncmaticaProtocol.sanitizeFileName(metaData.getName())); -+ buf.writeUUID(metaData.getHash()); -+ -+ if (exchangeTarget.getFeatureSet().hasFeature(Feature.CORE_EX)) { -+ buf.writeUUID(metaData.getOwner().uuid); -+ buf.writeUtf(metaData.getOwner().getName()); -+ buf.writeUUID(metaData.getLastModifiedBy().uuid); -+ buf.writeUtf(metaData.getLastModifiedBy().getName()); -+ } -+ -+ putPositionData(metaData, buf, exchangeTarget); -+ } -+ -+ public static void putPositionData(final @NotNull ServerPlacement metaData, final @NotNull FriendlyByteBuf buf, final @NotNull ExchangeTarget exchangeTarget) { -+ buf.writeBlockPos(metaData.getPosition()); -+ buf.writeUtf(metaData.getDimension()); -+ buf.writeInt(metaData.getRotation().ordinal()); -+ buf.writeInt(metaData.getMirror().ordinal()); -+ -+ if (exchangeTarget.getFeatureSet().hasFeature(Feature.CORE_EX)) { -+ if (metaData.getSubRegionData().getModificationData() == null) { -+ buf.writeInt(0); -+ return; -+ } -+ -+ final Collection regionData = metaData.getSubRegionData().getModificationData().values(); -+ buf.writeInt(regionData.size()); -+ -+ for (final SubRegionPlacementModification subPlacement : regionData) { -+ buf.writeUtf(subPlacement.name); -+ buf.writeBlockPos(subPlacement.position); -+ buf.writeInt(subPlacement.rotation.ordinal()); -+ buf.writeInt(subPlacement.mirror.ordinal()); -+ } -+ } -+ } -+ -+ public static ServerPlacement receiveMetaData(final @NotNull FriendlyByteBuf buf, final @NotNull ExchangeTarget exchangeTarget) { -+ final UUID id = buf.readUUID(); -+ -+ final String fileName = SyncmaticaProtocol.sanitizeFileName(buf.readUtf(32767)); -+ final UUID hash = buf.readUUID(); -+ -+ PlayerIdentifier owner = PlayerIdentifier.MISSING_PLAYER; -+ PlayerIdentifier lastModifiedBy = PlayerIdentifier.MISSING_PLAYER; -+ -+ if (exchangeTarget.getFeatureSet().hasFeature(Feature.CORE_EX)) { -+ final PlayerIdentifierProvider provider = SyncmaticaProtocol.getPlayerIdentifierProvider(); -+ owner = provider.createOrGet(buf.readUUID(), buf.readUtf(32767)); -+ lastModifiedBy = provider.createOrGet(buf.readUUID(), buf.readUtf(32767)); -+ } -+ -+ final ServerPlacement placement = new ServerPlacement(id, fileName, hash, owner); -+ placement.setLastModifiedBy(lastModifiedBy); -+ -+ receivePositionData(placement, buf, exchangeTarget); -+ -+ return placement; -+ } -+ -+ public static void receivePositionData(final @NotNull ServerPlacement placement, final @NotNull FriendlyByteBuf buf, final @NotNull ExchangeTarget exchangeTarget) { -+ final BlockPos pos = buf.readBlockPos(); -+ final String dimensionId = buf.readUtf(32767); -+ final Rotation rot = rotOrdinals[buf.readInt()]; -+ final Mirror mir = mirOrdinals[buf.readInt()]; -+ placement.move(dimensionId, pos, rot, mir); -+ -+ if (exchangeTarget.getFeatureSet().hasFeature(Feature.CORE_EX)) { -+ final SubRegionData subRegionData = placement.getSubRegionData(); -+ subRegionData.reset(); -+ final int limit = buf.readInt(); -+ for (int i = 0; i < limit; i++) { -+ subRegionData.modify(buf.readUtf(32767), buf.readBlockPos(), rotOrdinals[buf.readInt()], mirOrdinals[buf.readInt()]); -+ } -+ } -+ } -+ -+ public static void download(final ServerPlacement syncmatic, final ExchangeTarget source) throws NoSuchAlgorithmException, IOException { -+ if (!SyncmaticaProtocol.getFileStorage().getLocalState(syncmatic).isReadyForDownload()) { -+ throw new IllegalArgumentException(syncmatic.toString() + " is not ready for download local state is: " + SyncmaticaProtocol.getFileStorage().getLocalState(syncmatic).toString()); -+ } -+ final File toDownload = SyncmaticaProtocol.getFileStorage().createLocalLitematic(syncmatic); -+ final Exchange downloadExchange = new DownloadExchange(syncmatic, toDownload, source); -+ setDownloadState(syncmatic, true); -+ startExchange(downloadExchange); -+ } -+ -+ public static void setDownloadState(final @NotNull ServerPlacement syncmatic, final boolean b) { -+ downloadState.put(syncmatic.getHash(), b); -+ } -+ -+ public static boolean getDownloadState(final @NotNull ServerPlacement syncmatic) { -+ return downloadState.getOrDefault(syncmatic.getHash(), false); -+ } -+ -+ public static void setModifier(final @NotNull ServerPlacement syncmatic, final Exchange exchange) { -+ modifyState.put(syncmatic.getHash(), exchange); -+ } -+ -+ public static Exchange getModifier(final @NotNull ServerPlacement syncmatic) { -+ return modifyState.get(syncmatic.getHash()); -+ } -+ -+ public static void startExchange(final @NotNull Exchange newExchange) { -+ if (!broadcastTargets.contains(newExchange.getPartner())) { -+ throw new IllegalArgumentException(newExchange.getPartner().toString() + " is not a valid ExchangeTarget"); -+ } -+ startExchangeUnchecked(newExchange); -+ } -+ -+ protected static void startExchangeUnchecked(final @NotNull Exchange newExchange) { -+ newExchange.getPartner().getExchanges().add(newExchange); -+ newExchange.init(); -+ if (newExchange.isFinished()) { -+ notifyClose(newExchange); -+ } -+ } -+ -+ public static void notifyClose(final @NotNull Exchange e) { -+ e.getPartner().getExchanges().remove(e); -+ handleExchange(e); -+ } -+} -diff --git a/src/main/java/top/leavesmc/leaves/protocol/syncmatica/Feature.java b/src/main/java/top/leavesmc/leaves/protocol/syncmatica/Feature.java -new file mode 100644 -index 0000000000000000000000000000000000000000..1125755d7d78a118d1fe407e9ca554a89f4d9a9a ---- /dev/null -+++ b/src/main/java/top/leavesmc/leaves/protocol/syncmatica/Feature.java -@@ -0,0 +1,23 @@ -+package top.leavesmc.leaves.protocol.syncmatica; -+ -+import org.jetbrains.annotations.Nullable; -+ -+public enum Feature { -+ CORE, -+ FEATURE, -+ MODIFY, -+ MESSAGE, -+ QUOTA, -+ DEBUG, -+ CORE_EX; -+ -+ @Nullable -+ public static Feature fromString(final String s) { -+ for (final Feature f : Feature.values()) { -+ if (f.toString().equals(s)) { -+ return f; -+ } -+ } -+ return null; -+ } -+} -diff --git a/src/main/java/top/leavesmc/leaves/protocol/syncmatica/FeatureSet.java b/src/main/java/top/leavesmc/leaves/protocol/syncmatica/FeatureSet.java -new file mode 100644 -index 0000000000000000000000000000000000000000..3d851913e2016fcd384b6a8b1e91753cb8ea91ef ---- /dev/null -+++ b/src/main/java/top/leavesmc/leaves/protocol/syncmatica/FeatureSet.java -@@ -0,0 +1,67 @@ -+package top.leavesmc.leaves.protocol.syncmatica; -+ -+import org.jetbrains.annotations.NotNull; -+import org.jetbrains.annotations.Nullable; -+ -+import java.util.ArrayList; -+import java.util.Collection; -+import java.util.Collections; -+import java.util.HashMap; -+import java.util.Map; -+ -+public class FeatureSet { -+ -+ private static final Map versionFeatures; -+ private final Collection features; -+ -+ @Nullable -+ public static FeatureSet fromVersionString(@NotNull String version) { -+ if (version.matches("^\\d+(\\.\\d+){2,4}$")) { -+ final int minSize = version.indexOf("."); -+ while (version.length() > minSize) { -+ if (versionFeatures.containsKey(version)) { -+ return versionFeatures.get(version); -+ } -+ final int lastDot = version.lastIndexOf("."); -+ version = version.substring(0, lastDot); -+ } -+ } -+ return null; -+ } -+ -+ @NotNull -+ public static FeatureSet fromString(final @NotNull String features) { -+ final FeatureSet featureSet = new FeatureSet(new ArrayList<>()); -+ for (final String feature : features.split("\n")) { -+ final Feature f = Feature.fromString(feature); -+ if (f != null) { -+ featureSet.features.add(f); -+ } -+ } -+ return featureSet; -+ } -+ -+ @Override -+ public String toString() { -+ final StringBuilder output = new StringBuilder(); -+ boolean b = false; -+ for (final Feature feature : features) { -+ output.append(b ? "\n" + feature.toString() : feature.toString()); -+ b = true; -+ } -+ return output.toString(); -+ } -+ -+ public FeatureSet(final Collection features) { -+ this.features = features; -+ } -+ -+ public boolean hasFeature(final Feature f) { -+ return features.contains(f); -+ } -+ -+ static { -+ versionFeatures = new HashMap<>(); -+ versionFeatures.put("0.1", new FeatureSet(Collections.singletonList(Feature.CORE))); -+ } -+} -diff --git a/src/main/java/top/leavesmc/leaves/protocol/syncmatica/FileStorage.java b/src/main/java/top/leavesmc/leaves/protocol/syncmatica/FileStorage.java -new file mode 100644 -index 0000000000000000000000000000000000000000..5dccbce7287fe436de9436f35a7d1ffcfc5d74ab ---- /dev/null -+++ b/src/main/java/top/leavesmc/leaves/protocol/syncmatica/FileStorage.java -@@ -0,0 +1,80 @@ -+package top.leavesmc.leaves.protocol.syncmatica; -+ -+import org.jetbrains.annotations.Contract; -+import org.jetbrains.annotations.NotNull; -+ -+import java.io.File; -+import java.io.FileInputStream; -+import java.io.IOException; -+import java.util.HashMap; -+import java.util.UUID; -+ -+public class FileStorage { -+ -+ private final HashMap buffer = new HashMap<>(); -+ -+ public LocalLitematicState getLocalState(final ServerPlacement placement) { -+ final File localFile = getSchematicPath(placement); -+ if (localFile.isFile()) { -+ if (isDownloading(placement)) { -+ return LocalLitematicState.DOWNLOADING_LITEMATIC; -+ } -+ if ((buffer.containsKey(placement) && buffer.get(placement) == localFile.lastModified()) || hashCompare(localFile, placement)) { -+ return LocalLitematicState.LOCAL_LITEMATIC_PRESENT; -+ } -+ return LocalLitematicState.LOCAL_LITEMATIC_DESYNC; -+ } -+ return LocalLitematicState.NO_LOCAL_LITEMATIC; -+ } -+ -+ private boolean isDownloading(final ServerPlacement placement) { -+ return SyncmaticaProtocol.getCommunicationManager().getDownloadState(placement); -+ } -+ -+ public File getLocalLitematic(final ServerPlacement placement) { -+ if (getLocalState(placement).isLocalFileReady()) { -+ return getSchematicPath(placement); -+ } else { -+ return null; -+ } -+ } -+ -+ public File createLocalLitematic(final ServerPlacement placement) { -+ if (getLocalState(placement).isLocalFileReady()) { -+ throw new IllegalArgumentException(""); -+ } -+ final File file = getSchematicPath(placement); -+ if (file.exists()) { -+ file.delete(); -+ } -+ try { -+ file.createNewFile(); -+ } catch (final IOException e) { -+ e.printStackTrace(); -+ } -+ return file; -+ } -+ -+ private boolean hashCompare(final File localFile, final ServerPlacement placement) { -+ UUID hash = null; -+ try { -+ hash = SyncmaticaProtocol.createChecksum(new FileInputStream(localFile)); -+ } catch (final Exception e) { -+ e.printStackTrace(); -+ } -+ -+ if (hash == null) { -+ return false; -+ } -+ if (hash.equals(placement.getHash())) { -+ buffer.put(placement, localFile.lastModified()); -+ return true; -+ } -+ return false; -+ } -+ -+ @Contract("_ -> new") -+ private @NotNull File getSchematicPath(final @NotNull ServerPlacement placement) { -+ return new File(SyncmaticaProtocol.getLitematicFolder(), placement.getHash().toString() + ".litematic"); -+ } -+} -diff --git a/src/main/java/top/leavesmc/leaves/protocol/syncmatica/LocalLitematicState.java b/src/main/java/top/leavesmc/leaves/protocol/syncmatica/LocalLitematicState.java -new file mode 100644 -index 0000000000000000000000000000000000000000..82ffc8cbd1b488c8723693b685a91c2a4149fb47 ---- /dev/null -+++ b/src/main/java/top/leavesmc/leaves/protocol/syncmatica/LocalLitematicState.java -@@ -0,0 +1,24 @@ -+package top.leavesmc.leaves.protocol.syncmatica; -+ -+public enum LocalLitematicState { -+ NO_LOCAL_LITEMATIC(true, false), -+ LOCAL_LITEMATIC_DESYNC(true, false), -+ DOWNLOADING_LITEMATIC(false, false), -+ LOCAL_LITEMATIC_PRESENT(false, true); -+ -+ private final boolean downloadReady; -+ private final boolean fileReady; -+ -+ LocalLitematicState(final boolean downloadReady, final boolean fileReady) { -+ this.downloadReady = downloadReady; -+ this.fileReady = fileReady; -+ } -+ -+ public boolean isReadyForDownload() { -+ return downloadReady; -+ } -+ -+ public boolean isLocalFileReady() { -+ return fileReady; -+ } -+} -diff --git a/src/main/java/top/leavesmc/leaves/protocol/syncmatica/MessageType.java b/src/main/java/top/leavesmc/leaves/protocol/syncmatica/MessageType.java -new file mode 100644 -index 0000000000000000000000000000000000000000..04d785846be3670b741d90634f5f691899127835 ---- /dev/null -+++ b/src/main/java/top/leavesmc/leaves/protocol/syncmatica/MessageType.java -@@ -0,0 +1,8 @@ -+package top.leavesmc.leaves.protocol.syncmatica; -+ -+public enum MessageType { -+ SUCCESS, -+ INFO, -+ WARNING, -+ ERROR -+} -diff --git a/src/main/java/top/leavesmc/leaves/protocol/syncmatica/PacketType.java b/src/main/java/top/leavesmc/leaves/protocol/syncmatica/PacketType.java -new file mode 100644 -index 0000000000000000000000000000000000000000..8f3227d36da0a3055cc25e538437de58fd5730e3 ---- /dev/null -+++ b/src/main/java/top/leavesmc/leaves/protocol/syncmatica/PacketType.java -@@ -0,0 +1,30 @@ -+package top.leavesmc.leaves.protocol.syncmatica; -+ -+import net.minecraft.resources.ResourceLocation; -+ -+public enum PacketType { -+ REGISTER_METADATA("register_metadata"), -+ CANCEL_SHARE("cancel_share"), -+ REQUEST_LITEMATIC("request_download"), -+ SEND_LITEMATIC("send_litematic"), -+ RECEIVED_LITEMATIC("received_litematic"), -+ FINISHED_LITEMATIC("finished_litematic"), -+ CANCEL_LITEMATIC("cancel_litematic"), -+ REMOVE_SYNCMATIC("remove_syncmatic"), -+ REGISTER_VERSION("register_version"), -+ CONFIRM_USER("confirm_user"), -+ FEATURE_REQUEST("feature_request"), -+ FEATURE("feature"), -+ MODIFY("modify"), -+ MODIFY_REQUEST("modify_request"), -+ MODIFY_REQUEST_DENY("modify_request_deny"), -+ MODIFY_REQUEST_ACCEPT("modify_request_accept"), -+ MODIFY_FINISH("modify_finish"), -+ MESSAGE("mesage"); -+ -+ public final ResourceLocation identifier; -+ -+ PacketType(final String id) { -+ identifier = new ResourceLocation(SyncmaticaProtocol.PROTOCOL_ID, id); -+ } -+} -diff --git a/src/main/java/top/leavesmc/leaves/protocol/syncmatica/PlayerIdentifier.java b/src/main/java/top/leavesmc/leaves/protocol/syncmatica/PlayerIdentifier.java -new file mode 100644 -index 0000000000000000000000000000000000000000..f9ba2a41ab1e0d50bf85fd024b6d29e65b3a5cf7 ---- /dev/null -+++ b/src/main/java/top/leavesmc/leaves/protocol/syncmatica/PlayerIdentifier.java -@@ -0,0 +1,37 @@ -+package top.leavesmc.leaves.protocol.syncmatica; -+ -+import com.google.gson.JsonObject; -+import com.google.gson.JsonPrimitive; -+ -+import java.util.UUID; -+ -+public class PlayerIdentifier { -+ -+ public static final UUID MISSING_PLAYER_UUID = UUID.fromString("4c1b738f-56fa-4011-8273-498c972424ea"); -+ public static final PlayerIdentifier MISSING_PLAYER = new PlayerIdentifier(MISSING_PLAYER_UUID, "No Player"); -+ -+ public final UUID uuid; -+ private String bufferedPlayerName; -+ -+ PlayerIdentifier(final UUID uuid, final String bufferedPlayerName) { -+ this.uuid = uuid; -+ this.bufferedPlayerName = bufferedPlayerName; -+ } -+ -+ public String getName() { -+ return bufferedPlayerName; -+ } -+ -+ public void updatePlayerName(final String name) { -+ bufferedPlayerName = name; -+ } -+ -+ public JsonObject toJson() { -+ final JsonObject jsonObject = new JsonObject(); -+ -+ jsonObject.add("uuid", new JsonPrimitive(uuid.toString())); -+ jsonObject.add("name", new JsonPrimitive(bufferedPlayerName)); -+ -+ return jsonObject; -+ } -+} -diff --git a/src/main/java/top/leavesmc/leaves/protocol/syncmatica/PlayerIdentifierProvider.java b/src/main/java/top/leavesmc/leaves/protocol/syncmatica/PlayerIdentifierProvider.java -new file mode 100644 -index 0000000000000000000000000000000000000000..f4fc3bac20359ecf17a25d7b8e8f34cfebcf4b24 ---- /dev/null -+++ b/src/main/java/top/leavesmc/leaves/protocol/syncmatica/PlayerIdentifierProvider.java -@@ -0,0 +1,46 @@ -+package top.leavesmc.leaves.protocol.syncmatica; -+ -+import com.google.gson.JsonObject; -+import com.mojang.authlib.GameProfile; -+import org.jetbrains.annotations.NotNull; -+import top.leavesmc.leaves.protocol.syncmatica.exchange.ExchangeTarget; -+ -+import java.util.HashMap; -+import java.util.Map; -+import java.util.UUID; -+ -+public class PlayerIdentifierProvider { -+ -+ private final Map identifiers = new HashMap<>(); -+ -+ public PlayerIdentifierProvider() { -+ identifiers.put(PlayerIdentifier.MISSING_PLAYER_UUID, PlayerIdentifier.MISSING_PLAYER); -+ } -+ -+ public PlayerIdentifier createOrGet(final ExchangeTarget exchangeTarget) { -+ return createOrGet(SyncmaticaProtocol.getCommunicationManager().getGameProfile(exchangeTarget)); -+ } -+ -+ public PlayerIdentifier createOrGet(final @NotNull GameProfile gameProfile) { -+ return createOrGet(gameProfile.getId(), gameProfile.getName()); -+ } -+ -+ public PlayerIdentifier createOrGet(final UUID uuid, final String playerName) { -+ return identifiers.computeIfAbsent(uuid, id -> new PlayerIdentifier(uuid, playerName)); -+ } -+ -+ public void updateName(final UUID uuid, final String playerName) { -+ createOrGet(uuid, playerName).updatePlayerName(playerName); -+ } -+ -+ public PlayerIdentifier fromJson(final @NotNull JsonObject obj) { -+ if (!obj.has("uuid") || !obj.has("name")) { -+ return PlayerIdentifier.MISSING_PLAYER; -+ } -+ -+ final UUID jsonUUID = UUID.fromString(obj.get("uuid").getAsString()); -+ return identifiers.computeIfAbsent(jsonUUID, -+ key -> new PlayerIdentifier(jsonUUID, obj.get("name").getAsString()) -+ ); -+ } -+} -diff --git a/src/main/java/top/leavesmc/leaves/protocol/syncmatica/ServerPlacement.java b/src/main/java/top/leavesmc/leaves/protocol/syncmatica/ServerPlacement.java -new file mode 100644 -index 0000000000000000000000000000000000000000..8c5bc7d6244d6ccd9a561030fff44ccdecc1ed5c ---- /dev/null -+++ b/src/main/java/top/leavesmc/leaves/protocol/syncmatica/ServerPlacement.java -@@ -0,0 +1,166 @@ -+package top.leavesmc.leaves.protocol.syncmatica; -+ -+import com.google.gson.JsonObject; -+import com.google.gson.JsonPrimitive; -+import net.minecraft.core.BlockPos; -+import net.minecraft.world.level.block.Mirror; -+import net.minecraft.world.level.block.Rotation; -+import org.jetbrains.annotations.NotNull; -+import org.jetbrains.annotations.Nullable; -+ -+import java.util.UUID; -+ -+public class ServerPlacement { -+ -+ private final UUID id; -+ -+ private final String fileName; -+ private final UUID hashValue; -+ -+ private PlayerIdentifier owner; -+ private PlayerIdentifier lastModifiedBy; -+ -+ private ServerPosition origin; -+ private Rotation rotation; -+ private Mirror mirror; -+ -+ private SubRegionData subRegionData = new SubRegionData(); -+ -+ public ServerPlacement(final UUID id, final String fileName, final UUID hashValue, final PlayerIdentifier owner) { -+ this.id = id; -+ this.fileName = fileName; -+ this.hashValue = hashValue; -+ this.owner = owner; -+ lastModifiedBy = owner; -+ } -+ -+ public UUID getId() { -+ return id; -+ } -+ -+ public String getName() { -+ return fileName; -+ } -+ -+ public UUID getHash() { -+ return hashValue; -+ } -+ -+ public String getDimension() { -+ return origin.getDimensionId(); -+ } -+ -+ public BlockPos getPosition() { -+ return origin.getBlockPosition(); -+ } -+ -+ public ServerPosition getOrigin() { -+ return origin; -+ } -+ -+ public Rotation getRotation() { -+ return rotation; -+ } -+ -+ public Mirror getMirror() { -+ return mirror; -+ } -+ -+ public ServerPlacement move(final String dimensionId, final BlockPos origin, final Rotation rotation, final Mirror mirror) { -+ move(new ServerPosition(origin, dimensionId), rotation, mirror); -+ return this; -+ } -+ -+ public ServerPlacement move(final ServerPosition origin, final Rotation rotation, final Mirror mirror) { -+ this.origin = origin; -+ this.rotation = rotation; -+ this.mirror = mirror; -+ return this; -+ } -+ -+ public PlayerIdentifier getOwner() { -+ return owner; -+ } -+ -+ public void setOwner(final PlayerIdentifier playerIdentifier) { -+ owner = playerIdentifier; -+ } -+ -+ public PlayerIdentifier getLastModifiedBy() { -+ return lastModifiedBy; -+ } -+ -+ public void setLastModifiedBy(final PlayerIdentifier lastModifiedBy) { -+ this.lastModifiedBy = lastModifiedBy; -+ } -+ -+ public SubRegionData getSubRegionData() { -+ return subRegionData; -+ } -+ -+ public JsonObject toJson() { -+ final JsonObject obj = new JsonObject(); -+ obj.add("id", new JsonPrimitive(id.toString())); -+ -+ obj.add("file_name", new JsonPrimitive(fileName)); -+ obj.add("hash", new JsonPrimitive(hashValue.toString())); -+ -+ obj.add("origin", origin.toJson()); -+ obj.add("rotation", new JsonPrimitive(rotation.name())); -+ obj.add("mirror", new JsonPrimitive(mirror.name())); -+ -+ obj.add("owner", owner.toJson()); -+ if (!owner.equals(lastModifiedBy)) { -+ obj.add("lastModifiedBy", lastModifiedBy.toJson()); -+ } -+ -+ if (subRegionData.isModified()) { -+ obj.add("subregionData", subRegionData.toJson()); -+ } -+ -+ return obj; -+ } -+ -+ @Nullable -+ public static ServerPlacement fromJson(final @NotNull JsonObject obj) { -+ if (obj.has("id") -+ && obj.has("file_name") -+ && obj.has("hash") -+ && obj.has("origin") -+ && obj.has("rotation") -+ && obj.has("mirror")) { -+ final UUID id = UUID.fromString(obj.get("id").getAsString()); -+ final String name = obj.get("file_name").getAsString(); -+ final UUID hashValue = UUID.fromString(obj.get("hash").getAsString()); -+ -+ PlayerIdentifier owner = PlayerIdentifier.MISSING_PLAYER; -+ if (obj.has("owner")) { -+ owner = SyncmaticaProtocol.getPlayerIdentifierProvider().fromJson(obj.get("owner").getAsJsonObject()); -+ } -+ -+ final ServerPlacement newPlacement = new ServerPlacement(id, name, hashValue, owner); -+ final ServerPosition pos = ServerPosition.fromJson(obj.get("origin").getAsJsonObject()); -+ if (pos == null) { -+ return null; -+ } -+ newPlacement.origin = pos; -+ newPlacement.rotation = Rotation.valueOf(obj.get("rotation").getAsString()); -+ newPlacement.mirror = Mirror.valueOf(obj.get("mirror").getAsString()); -+ -+ if (obj.has("lastModifiedBy")) { -+ newPlacement.lastModifiedBy = SyncmaticaProtocol.getPlayerIdentifierProvider() -+ .fromJson(obj.get("lastModifiedBy").getAsJsonObject()); -+ } else { -+ newPlacement.lastModifiedBy = owner; -+ } -+ -+ if (obj.has("subregionData")) { -+ newPlacement.subRegionData = SubRegionData.fromJson(obj.get("subregionData")); -+ } -+ -+ return newPlacement; -+ } -+ -+ return null; -+ } -+} -diff --git a/src/main/java/top/leavesmc/leaves/protocol/syncmatica/ServerPosition.java b/src/main/java/top/leavesmc/leaves/protocol/syncmatica/ServerPosition.java -new file mode 100644 -index 0000000000000000000000000000000000000000..3f6ee21ce72943e11f8d924321eb286652c5c533 ---- /dev/null -+++ b/src/main/java/top/leavesmc/leaves/protocol/syncmatica/ServerPosition.java -@@ -0,0 +1,51 @@ -+package top.leavesmc.leaves.protocol.syncmatica; -+ -+import com.google.gson.JsonArray; -+import com.google.gson.JsonObject; -+import com.google.gson.JsonPrimitive; -+import net.minecraft.core.BlockPos; -+ -+public class ServerPosition { -+ -+ private final BlockPos position; -+ private final String dimensionId; -+ -+ public ServerPosition(final BlockPos pos, final String dim) { -+ position = pos; -+ dimensionId = dim; -+ } -+ -+ public BlockPos getBlockPosition() { -+ return position; -+ } -+ -+ public String getDimensionId() { -+ return dimensionId; -+ } -+ -+ public JsonObject toJson() { -+ final JsonObject obj = new JsonObject(); -+ final JsonArray arr = new JsonArray(); -+ arr.add(new JsonPrimitive(position.getX())); -+ arr.add(new JsonPrimitive(position.getY())); -+ arr.add(new JsonPrimitive(position.getZ())); -+ obj.add("position", arr); -+ obj.add("dimension", new JsonPrimitive(dimensionId)); -+ return obj; -+ } -+ -+ public static ServerPosition fromJson(final JsonObject obj) { -+ if (obj.has("position") && obj.has("dimension")) { -+ final int x; -+ final int y; -+ final int z; -+ final JsonArray arr = obj.get("position").getAsJsonArray(); -+ x = arr.get(0).getAsInt(); -+ y = arr.get(1).getAsInt(); -+ z = arr.get(2).getAsInt(); -+ final BlockPos pos = new BlockPos(x, y, z); -+ return new ServerPosition(pos, obj.get("dimension").getAsString()); -+ } -+ return null; -+ } -+} -diff --git a/src/main/java/top/leavesmc/leaves/protocol/syncmatica/SubRegionData.java b/src/main/java/top/leavesmc/leaves/protocol/syncmatica/SubRegionData.java -new file mode 100644 -index 0000000000000000000000000000000000000000..6903c26742f5e10aa75f52b7abd5273e7116600b ---- /dev/null -+++ b/src/main/java/top/leavesmc/leaves/protocol/syncmatica/SubRegionData.java -@@ -0,0 +1,90 @@ -+package top.leavesmc.leaves.protocol.syncmatica; -+ -+import com.google.gson.JsonArray; -+import com.google.gson.JsonElement; -+import net.minecraft.core.BlockPos; -+import net.minecraft.world.level.block.Mirror; -+import net.minecraft.world.level.block.Rotation; -+import org.jetbrains.annotations.NotNull; -+ -+import java.util.HashMap; -+import java.util.Map; -+ -+public class SubRegionData { -+ -+ private boolean isModified; -+ private Map modificationData; // is null when isModified is false -+ -+ public SubRegionData() { -+ this(false, null); -+ } -+ -+ public SubRegionData(final boolean isModified, final Map modificationData) { -+ this.isModified = isModified; -+ this.modificationData = modificationData; -+ } -+ -+ public void reset() { -+ isModified = false; -+ modificationData = null; -+ } -+ -+ public void modify(final String name, final BlockPos position, final Rotation rotation, final Mirror mirror) { -+ modify(new SubRegionPlacementModification(name, position, rotation, mirror)); -+ } -+ -+ public void modify(final SubRegionPlacementModification subRegionPlacementModification) { -+ if (subRegionPlacementModification == null) { -+ return; -+ } -+ isModified = true; -+ if (modificationData == null) { -+ modificationData = new HashMap<>(); -+ } -+ modificationData.put(subRegionPlacementModification.name, subRegionPlacementModification); -+ } -+ -+ public boolean isModified() { -+ return isModified; -+ } -+ -+ public Map getModificationData() { -+ return modificationData; -+ } -+ -+ public JsonElement toJson() { -+ return modificationDataToJson(); -+ } -+ -+ @NotNull -+ private JsonElement modificationDataToJson() { -+ final JsonArray arr = new JsonArray(); -+ -+ for (final Map.Entry entry : modificationData.entrySet()) { -+ arr.add(entry.getValue().toJson()); -+ } -+ -+ return arr; -+ } -+ -+ @NotNull -+ public static SubRegionData fromJson(final @NotNull JsonElement obj) { -+ final SubRegionData newSubRegionData = new SubRegionData(); -+ -+ newSubRegionData.isModified = true; -+ -+ for (final JsonElement modification : obj.getAsJsonArray()) { -+ newSubRegionData.modify(SubRegionPlacementModification.fromJson(modification.getAsJsonObject())); -+ } -+ -+ return newSubRegionData; -+ } -+ -+ @Override -+ public String toString() { -+ if (!isModified) { -+ return "[]"; -+ } -+ return modificationData == null ? "[ERROR:null]" : modificationData.toString(); -+ } -+} -diff --git a/src/main/java/top/leavesmc/leaves/protocol/syncmatica/SubRegionPlacementModification.java b/src/main/java/top/leavesmc/leaves/protocol/syncmatica/SubRegionPlacementModification.java -new file mode 100644 -index 0000000000000000000000000000000000000000..0d67b562ed06f8de990c2f3d545e2839837f853d ---- /dev/null -+++ b/src/main/java/top/leavesmc/leaves/protocol/syncmatica/SubRegionPlacementModification.java -@@ -0,0 +1,65 @@ -+package top.leavesmc.leaves.protocol.syncmatica; -+ -+import com.google.gson.JsonArray; -+import com.google.gson.JsonObject; -+import com.google.gson.JsonPrimitive; -+import net.minecraft.core.BlockPos; -+import net.minecraft.world.level.block.Mirror; -+import net.minecraft.world.level.block.Rotation; -+import org.jetbrains.annotations.NotNull; -+import org.jetbrains.annotations.Nullable; -+ -+public class SubRegionPlacementModification { -+ -+ public final String name; -+ public final BlockPos position; -+ public final Rotation rotation; -+ public final Mirror mirror; -+ -+ SubRegionPlacementModification(final String name, final BlockPos position, final Rotation rotation, final Mirror mirror) { -+ this.name = name; -+ this.position = position; -+ this.rotation = rotation; -+ this.mirror = mirror; -+ } -+ -+ public JsonObject toJson() { -+ final JsonObject obj = new JsonObject(); -+ -+ final JsonArray arr = new JsonArray(); -+ arr.add(position.getX()); -+ arr.add(position.getY()); -+ arr.add(position.getZ()); -+ obj.add("position", arr); -+ -+ obj.add("name", new JsonPrimitive(name)); -+ obj.add("rotation", new JsonPrimitive(rotation.name())); -+ obj.add("mirror", new JsonPrimitive(mirror.name())); -+ -+ return obj; -+ } -+ -+ @Nullable -+ public static SubRegionPlacementModification fromJson(final @NotNull JsonObject obj) { -+ if (!obj.has("name") || !obj.has("position") || !obj.has("rotation") || !obj.has("mirror")) { -+ return null; -+ } -+ -+ final String name = obj.get("name").getAsString(); -+ final JsonArray arr = obj.get("position").getAsJsonArray(); -+ if (arr.size() != 3) { -+ return null; -+ } -+ -+ final BlockPos position = new BlockPos(arr.get(0).getAsInt(), arr.get(1).getAsInt(), arr.get(2).getAsInt()); -+ final Rotation rotation = Rotation.valueOf(obj.get("rotation").getAsString()); -+ final Mirror mirror = Mirror.valueOf(obj.get("mirror").getAsString()); -+ -+ return new SubRegionPlacementModification(name, position, rotation, mirror); -+ } -+ -+ @Override -+ public String toString() { -+ return String.format("[name=%s, position=%s, rotation=%s, mirror=%s]", name, position, rotation, mirror); -+ } -+} -diff --git a/src/main/java/top/leavesmc/leaves/protocol/syncmatica/SyncmaticManager.java b/src/main/java/top/leavesmc/leaves/protocol/syncmatica/SyncmaticManager.java -new file mode 100644 -index 0000000000000000000000000000000000000000..9fbeef1ef528504276895faed4dba41ee0789e77 ---- /dev/null -+++ b/src/main/java/top/leavesmc/leaves/protocol/syncmatica/SyncmaticManager.java -@@ -0,0 +1,108 @@ -+package top.leavesmc.leaves.protocol.syncmatica; -+ -+import com.google.gson.GsonBuilder; -+import com.google.gson.JsonArray; -+import com.google.gson.JsonElement; -+import com.google.gson.JsonObject; -+import com.google.gson.JsonParser; -+import org.jetbrains.annotations.NotNull; -+ -+import java.io.File; -+import java.io.FileReader; -+import java.io.FileWriter; -+import java.io.IOException; -+import java.util.Collection; -+import java.util.HashMap; -+import java.util.Map; -+import java.util.UUID; -+ -+public class SyncmaticManager { -+ -+ public static final String PLACEMENTS_JSON_KEY = "placements"; -+ private final Map schematics = new HashMap<>(); -+ -+ public void addPlacement(final ServerPlacement placement) { -+ schematics.put(placement.getId(), placement); -+ updateServerPlacement(); -+ } -+ -+ public ServerPlacement getPlacement(final UUID id) { -+ return schematics.get(id); -+ } -+ -+ public Collection getAll() { -+ return schematics.values(); -+ } -+ -+ public void removePlacement(final @NotNull ServerPlacement placement) { -+ schematics.remove(placement.getId()); -+ updateServerPlacement(); -+ } -+ -+ public void updateServerPlacement() { -+ saveServer(); -+ } -+ -+ public void startup() { -+ loadServer(); -+ } -+ -+ private void saveServer() { -+ final JsonObject obj = new JsonObject(); -+ final JsonArray arr = new JsonArray(); -+ -+ for (final ServerPlacement p : getAll()) { -+ arr.add(p.toJson()); -+ } -+ -+ obj.add(PLACEMENTS_JSON_KEY, arr); -+ final File backup = new File(SyncmaticaProtocol.getLitematicFolder(), "placements.json.bak"); -+ final File incoming = new File(SyncmaticaProtocol.getLitematicFolder(), "placements.json.new"); -+ final File current = new File(SyncmaticaProtocol.getLitematicFolder(), "placements.json"); -+ -+ try (final FileWriter writer = new FileWriter(incoming)) { -+ writer.write(new GsonBuilder().setPrettyPrinting().create().toJson(obj)); -+ } catch (final IOException e) { -+ e.printStackTrace(); -+ return; -+ } -+ -+ SyncmaticaProtocol.backupAndReplace(backup.toPath(), current.toPath(), incoming.toPath()); -+ } -+ -+ private void loadServer() { -+ final File f = new File(SyncmaticaProtocol.getLitematicFolder(), "placements.json"); -+ if (f.exists() && f.isFile() && f.canRead()) { -+ JsonElement element = null; -+ try { -+ final JsonParser parser = new JsonParser(); -+ final FileReader reader = new FileReader(f); -+ -+ element = parser.parse(reader); -+ reader.close(); -+ -+ } catch (final Exception e) { -+ e.printStackTrace(); -+ } -+ if (element == null) { -+ return; -+ } -+ try { -+ final JsonObject obj = element.getAsJsonObject(); -+ if (obj == null || !obj.has(PLACEMENTS_JSON_KEY)) { -+ return; -+ } -+ final JsonArray arr = obj.getAsJsonArray(PLACEMENTS_JSON_KEY); -+ for (final JsonElement elem : arr) { -+ final ServerPlacement placement = ServerPlacement.fromJson(elem.getAsJsonObject()); -+ if (placement != null) { -+ schematics.put(placement.getId(), placement); -+ } -+ } -+ -+ } catch (final IllegalStateException | NullPointerException e) { -+ e.printStackTrace(); -+ } -+ } -+ } -+} -diff --git a/src/main/java/top/leavesmc/leaves/protocol/syncmatica/SyncmaticaProtocol.java b/src/main/java/top/leavesmc/leaves/protocol/syncmatica/SyncmaticaProtocol.java -new file mode 100644 -index 0000000000000000000000000000000000000000..2f44c35e199205ac2a9430c68a6ada3ef92d5d38 ---- /dev/null -+++ b/src/main/java/top/leavesmc/leaves/protocol/syncmatica/SyncmaticaProtocol.java -@@ -0,0 +1,123 @@ -+package top.leavesmc.leaves.protocol.syncmatica; -+ -+import org.jetbrains.annotations.NotNull; -+import top.leavesmc.leaves.LeavesConfig; -+ -+import java.io.File; -+import java.io.IOException; -+import java.io.InputStream; -+import java.nio.file.Files; -+import java.nio.file.Path; -+import java.security.MessageDigest; -+import java.security.NoSuchAlgorithmException; -+import java.util.Arrays; -+import java.util.UUID; -+ -+public class SyncmaticaProtocol { -+ -+ public static final String PROTOCOL_ID = "syncmatica"; -+ public static final String PROTOCOL_VERSION = "leaves-syncmatica-1.0.0"; -+ -+ private static final File litematicFolder = new File("." + File.separator + "syncmatics"); -+ private static final PlayerIdentifierProvider playerIdentifierProvider = new PlayerIdentifierProvider(); -+ private static final CommunicationManager communicationManager = new CommunicationManager(); -+ private static final FeatureSet featureSet = new FeatureSet(Arrays.asList(Feature.values())); -+ private static final SyncmaticManager syncmaticManager = new SyncmaticManager(); -+ private static final FileStorage fileStorage = new FileStorage(); -+ -+ public static File getLitematicFolder() { -+ return litematicFolder; -+ } -+ -+ public static PlayerIdentifierProvider getPlayerIdentifierProvider() { -+ return playerIdentifierProvider; -+ } -+ -+ public static CommunicationManager getCommunicationManager() { -+ return communicationManager; -+ } -+ -+ public static FeatureSet getFeatureSet() { -+ return featureSet; -+ } -+ -+ public static SyncmaticManager getSyncmaticManager() { -+ return syncmaticManager; -+ } -+ -+ public static FileStorage getFileStorage() { -+ return fileStorage; -+ } -+ -+ public static void init() { -+ litematicFolder.mkdirs(); -+ syncmaticManager.startup(); -+ } -+ -+ @NotNull -+ public static UUID createChecksum(final @NotNull InputStream fis) throws NoSuchAlgorithmException, IOException { -+ final byte[] buffer = new byte[4096]; -+ final MessageDigest messageDigest = MessageDigest.getInstance("MD5"); -+ int numRead; -+ -+ do { -+ numRead = fis.read(buffer); -+ if (numRead > 0) { -+ messageDigest.update(buffer, 0, numRead); -+ } -+ } while (numRead != -1); -+ -+ fis.close(); -+ return UUID.nameUUIDFromBytes(messageDigest.digest()); -+ } -+ -+ private static final int[] ILLEGAL_CHARS = {34, 60, 62, 124, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 58, 42, 63, 92, 47}; -+ private static final String ILLEGAL_PATTERNS = "(^(con|prn|aux|nul|com[0-9]|lpt[0-9])(\\..*)?$)|(^\\.\\.*$)"; -+ -+ @NotNull -+ public static String sanitizeFileName(final @NotNull String badFileName) { -+ final StringBuilder sanitized = new StringBuilder(); -+ final int len = badFileName.codePointCount(0, badFileName.length()); -+ -+ for (int i = 0; i < len; i++) { -+ final int c = badFileName.codePointAt(i); -+ if (Arrays.binarySearch(ILLEGAL_CHARS, c) < 0) { -+ sanitized.appendCodePoint(c); -+ if (sanitized.length() == 255) { -+ break; -+ } -+ } -+ } -+ -+ return sanitized.toString().replaceAll(ILLEGAL_PATTERNS, "_"); -+ } -+ -+ public static boolean isOverQuota(int sent) { -+ return LeavesConfig.syncmaticaQuota && sent > LeavesConfig.syncmaticaQuotaLimit; -+ } -+ -+ public static void backupAndReplace(final Path backup, final Path current, final Path incoming) { -+ if (!Files.exists(incoming)) { -+ return; -+ } -+ if (overwrite(backup, current, 2) && !overwrite(current, incoming, 4)) { -+ overwrite(current, backup, 8); -+ } -+ } -+ -+ private static boolean overwrite(final Path backup, final Path current, final int tries) { -+ if (!Files.exists(current)) { -+ return true; -+ } -+ try { -+ Files.deleteIfExists(backup); -+ Files.move(current, backup); -+ } catch (final IOException exception) { -+ if (tries <= 0) { -+ return false; -+ } -+ return overwrite(backup, current, tries - 1); -+ } -+ return true; -+ } -+} -diff --git a/src/main/java/top/leavesmc/leaves/protocol/syncmatica/exchange/AbstractExchange.java b/src/main/java/top/leavesmc/leaves/protocol/syncmatica/exchange/AbstractExchange.java -new file mode 100644 -index 0000000000000000000000000000000000000000..625974f9ce0791b476336abafa6aa1af2f2ffbac ---- /dev/null -+++ b/src/main/java/top/leavesmc/leaves/protocol/syncmatica/exchange/AbstractExchange.java -@@ -0,0 +1,66 @@ -+package top.leavesmc.leaves.protocol.syncmatica.exchange; -+ -+import net.minecraft.network.FriendlyByteBuf; -+import top.leavesmc.leaves.protocol.syncmatica.CommunicationManager; -+import top.leavesmc.leaves.protocol.syncmatica.SyncmaticaProtocol; -+ -+import java.util.UUID; -+ -+public abstract class AbstractExchange implements Exchange { -+ -+ private boolean success = false; -+ private boolean finished = false; -+ private final ExchangeTarget partner; -+ -+ protected AbstractExchange(final ExchangeTarget partner) { -+ this.partner = partner; -+ } -+ -+ @Override -+ public ExchangeTarget getPartner() { -+ return partner; -+ } -+ -+ @Override -+ public boolean isFinished() { -+ return finished; -+ } -+ -+ @Override -+ public boolean isSuccessful() { -+ return success; -+ } -+ -+ @Override -+ public void close(final boolean notifyPartner) { -+ finished = true; -+ success = false; -+ onClose(); -+ if (notifyPartner) { -+ sendCancelPacket(); -+ } -+ } -+ -+ public CommunicationManager getManager() { -+ return SyncmaticaProtocol.getCommunicationManager(); -+ } -+ -+ protected void sendCancelPacket() { -+ } -+ -+ protected void onClose() { -+ } -+ -+ protected void succeed() { -+ finished = true; -+ success = true; -+ onClose(); -+ } -+ -+ protected static boolean checkUUID(final FriendlyByteBuf sourceBuf, final UUID targetId) { -+ final int r = sourceBuf.readerIndex(); -+ final UUID sourceId = sourceBuf.readUUID(); -+ sourceBuf.readerIndex(r); -+ return sourceId.equals(targetId); -+ } -+} -diff --git a/src/main/java/top/leavesmc/leaves/protocol/syncmatica/exchange/DownloadExchange.java b/src/main/java/top/leavesmc/leaves/protocol/syncmatica/exchange/DownloadExchange.java -new file mode 100644 -index 0000000000000000000000000000000000000000..7303769570656f36a3a76215e22020b1292007fb ---- /dev/null -+++ b/src/main/java/top/leavesmc/leaves/protocol/syncmatica/exchange/DownloadExchange.java -@@ -0,0 +1,125 @@ -+package top.leavesmc.leaves.protocol.syncmatica.exchange; -+ -+import io.netty.buffer.Unpooled; -+import net.minecraft.network.FriendlyByteBuf; -+import net.minecraft.resources.ResourceLocation; -+import org.jetbrains.annotations.NotNull; -+import top.leavesmc.leaves.protocol.syncmatica.MessageType; -+import top.leavesmc.leaves.protocol.syncmatica.PacketType; -+import top.leavesmc.leaves.protocol.syncmatica.ServerPlacement; -+import top.leavesmc.leaves.protocol.syncmatica.SyncmaticaProtocol; -+ -+import java.io.File; -+import java.io.FileOutputStream; -+import java.io.IOException; -+import java.io.OutputStream; -+import java.security.DigestOutputStream; -+import java.security.MessageDigest; -+import java.security.NoSuchAlgorithmException; -+import java.util.UUID; -+ -+public class DownloadExchange extends AbstractExchange { -+ -+ private final ServerPlacement toDownload; -+ private final OutputStream outputStream; -+ private final MessageDigest md5; -+ private final File downloadFile; -+ private int bytesSent; -+ -+ public DownloadExchange(final ServerPlacement syncmatic, final File downloadFile, final ExchangeTarget partner) throws IOException, NoSuchAlgorithmException { -+ super(partner); -+ this.downloadFile = downloadFile; -+ final OutputStream os = new FileOutputStream(downloadFile); -+ toDownload = syncmatic; -+ md5 = MessageDigest.getInstance("MD5"); -+ outputStream = new DigestOutputStream(os, md5); -+ } -+ -+ @Override -+ public boolean checkPacket(final @NotNull ResourceLocation id, final FriendlyByteBuf packetBuf) { -+ if (id.equals(PacketType.SEND_LITEMATIC.identifier) -+ || id.equals(PacketType.FINISHED_LITEMATIC.identifier) -+ || id.equals(PacketType.CANCEL_LITEMATIC.identifier)) { -+ return checkUUID(packetBuf, toDownload.getId()); -+ } -+ return false; -+ } -+ -+ @Override -+ public void handle(final @NotNull ResourceLocation id, final @NotNull FriendlyByteBuf packetBuf) { -+ packetBuf.readUUID(); -+ if (id.equals(PacketType.SEND_LITEMATIC.identifier)) { -+ final int size = packetBuf.readInt(); -+ bytesSent += size; -+ if (SyncmaticaProtocol.isOverQuota(bytesSent)) { -+ close(true); -+ SyncmaticaProtocol.getCommunicationManager().sendMessage( -+ getPartner(), -+ MessageType.ERROR, -+ "syncmatica.error.cancelled_transmit_exceed_quota" -+ ); -+ } -+ try { -+ packetBuf.readBytes(outputStream, size); -+ } catch (final IOException e) { -+ close(true); -+ e.printStackTrace(); -+ return; -+ } -+ final FriendlyByteBuf FriendlyByteBuf = new FriendlyByteBuf(Unpooled.buffer()); -+ FriendlyByteBuf.writeUUID(toDownload.getId()); -+ getPartner().sendPacket(PacketType.RECEIVED_LITEMATIC.identifier, FriendlyByteBuf); -+ return; -+ } -+ if (id.equals(PacketType.FINISHED_LITEMATIC.identifier)) { -+ try { -+ outputStream.flush(); -+ } catch (final IOException e) { -+ close(false); -+ e.printStackTrace(); -+ return; -+ } -+ final UUID downloadHash = UUID.nameUUIDFromBytes(md5.digest()); -+ if (downloadHash.equals(toDownload.getHash())) { -+ succeed(); -+ } else { -+ close(false); -+ } -+ return; -+ } -+ if (id.equals(PacketType.CANCEL_LITEMATIC.identifier)) { -+ close(false); -+ } -+ } -+ -+ @Override -+ public void init() { -+ final FriendlyByteBuf FriendlyByteBuf = new FriendlyByteBuf(Unpooled.buffer()); -+ FriendlyByteBuf.writeUUID(toDownload.getId()); -+ getPartner().sendPacket(PacketType.REQUEST_LITEMATIC.identifier, FriendlyByteBuf); -+ } -+ -+ @Override -+ protected void onClose() { -+ getManager().setDownloadState(toDownload, false); -+ try { -+ outputStream.close(); -+ } catch (final IOException e) { -+ e.printStackTrace(); -+ } -+ if (!isSuccessful() && downloadFile.exists()) { -+ downloadFile.delete(); -+ } -+ } -+ -+ @Override -+ protected void sendCancelPacket() { -+ final FriendlyByteBuf FriendlyByteBuf = new FriendlyByteBuf(Unpooled.buffer()); -+ FriendlyByteBuf.writeUUID(toDownload.getId()); -+ getPartner().sendPacket(PacketType.CANCEL_LITEMATIC.identifier, FriendlyByteBuf); -+ } -+ -+ public ServerPlacement getPlacement() { -+ return toDownload; -+ } -+} -diff --git a/src/main/java/top/leavesmc/leaves/protocol/syncmatica/exchange/Exchange.java b/src/main/java/top/leavesmc/leaves/protocol/syncmatica/exchange/Exchange.java -new file mode 100644 -index 0000000000000000000000000000000000000000..26482e63b7c24c80bdc111cea51b8d7b8052d64e ---- /dev/null -+++ b/src/main/java/top/leavesmc/leaves/protocol/syncmatica/exchange/Exchange.java -@@ -0,0 +1,20 @@ -+package top.leavesmc.leaves.protocol.syncmatica.exchange; -+ -+import net.minecraft.network.FriendlyByteBuf; -+import net.minecraft.resources.ResourceLocation; -+ -+public interface Exchange { -+ ExchangeTarget getPartner(); -+ -+ boolean checkPacket(ResourceLocation id, FriendlyByteBuf packetBuf); -+ -+ void handle(ResourceLocation id, FriendlyByteBuf packetBuf); -+ -+ boolean isFinished(); -+ -+ boolean isSuccessful(); -+ -+ void close(boolean notifyPartner); -+ -+ void init(); -+} -diff --git a/src/main/java/top/leavesmc/leaves/protocol/syncmatica/exchange/ExchangeTarget.java b/src/main/java/top/leavesmc/leaves/protocol/syncmatica/exchange/ExchangeTarget.java -new file mode 100644 -index 0000000000000000000000000000000000000000..706680a3d7fae22f94cb86b8da2e306cfcf4cb1b ---- /dev/null -+++ b/src/main/java/top/leavesmc/leaves/protocol/syncmatica/exchange/ExchangeTarget.java -@@ -0,0 +1,40 @@ -+package top.leavesmc.leaves.protocol.syncmatica.exchange; -+ -+import net.minecraft.network.FriendlyByteBuf; -+import net.minecraft.resources.ResourceLocation; -+import net.minecraft.server.network.ServerGamePacketListenerImpl; -+import top.leavesmc.leaves.protocol.core.ProtocolUtils; -+import top.leavesmc.leaves.protocol.syncmatica.FeatureSet; -+ -+import java.util.ArrayList; -+import java.util.Collection; -+import java.util.List; -+ -+public class ExchangeTarget { -+ -+ private final List ongoingExchanges = new ArrayList<>(); -+ private final ServerGamePacketListenerImpl client; -+ private FeatureSet features; -+ -+ public ExchangeTarget(final ServerGamePacketListenerImpl client) { -+ this.client = client; -+ } -+ -+ public void sendPacket(final ResourceLocation id, final FriendlyByteBuf packetBuf) { -+ ProtocolUtils.sendPayloadPacket(client.player, id, buf -> { -+ buf.writeBytes(packetBuf); -+ }); -+ } -+ -+ public FeatureSet getFeatureSet() { -+ return features; -+ } -+ -+ public void setFeatureSet(final FeatureSet f) { -+ features = f; -+ } -+ -+ public Collection getExchanges() { -+ return ongoingExchanges; -+ } -+} -diff --git a/src/main/java/top/leavesmc/leaves/protocol/syncmatica/exchange/FeatureExchange.java b/src/main/java/top/leavesmc/leaves/protocol/syncmatica/exchange/FeatureExchange.java -new file mode 100644 -index 0000000000000000000000000000000000000000..f92739dbfa00de0e078834818dab79e34fc3d245 ---- /dev/null -+++ b/src/main/java/top/leavesmc/leaves/protocol/syncmatica/exchange/FeatureExchange.java -@@ -0,0 +1,48 @@ -+package top.leavesmc.leaves.protocol.syncmatica.exchange; -+ -+import io.netty.buffer.Unpooled; -+import net.minecraft.network.FriendlyByteBuf; -+import net.minecraft.resources.ResourceLocation; -+import org.jetbrains.annotations.NotNull; -+import top.leavesmc.leaves.protocol.syncmatica.FeatureSet; -+import top.leavesmc.leaves.protocol.syncmatica.PacketType; -+import top.leavesmc.leaves.protocol.syncmatica.SyncmaticaProtocol; -+ -+public abstract class FeatureExchange extends AbstractExchange { -+ -+ protected FeatureExchange(final ExchangeTarget partner) { -+ super(partner); -+ } -+ -+ @Override -+ public boolean checkPacket(final @NotNull ResourceLocation id, final FriendlyByteBuf packetBuf) { -+ return id.equals(PacketType.FEATURE_REQUEST.identifier) -+ || id.equals(PacketType.FEATURE.identifier); -+ } -+ -+ @Override -+ public void handle(final @NotNull ResourceLocation id, final FriendlyByteBuf packetBuf) { -+ if (id.equals(PacketType.FEATURE_REQUEST.identifier)) { -+ sendFeatures(); -+ } else if (id.equals(PacketType.FEATURE.identifier)) { -+ final FeatureSet fs = FeatureSet.fromString(packetBuf.readUtf(32767)); -+ getPartner().setFeatureSet(fs); -+ onFeatureSetReceive(); -+ } -+ } -+ -+ protected void onFeatureSetReceive() { -+ succeed(); -+ } -+ -+ public void requestFeatureSet() { -+ getPartner().sendPacket(PacketType.FEATURE_REQUEST.identifier, new FriendlyByteBuf(Unpooled.buffer())); -+ } -+ -+ private void sendFeatures() { -+ final FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer()); -+ final FeatureSet fs = SyncmaticaProtocol.getFeatureSet(); -+ buf.writeUtf(fs.toString(), 32767); -+ getPartner().sendPacket(PacketType.FEATURE.identifier, buf); -+ } -+} -diff --git a/src/main/java/top/leavesmc/leaves/protocol/syncmatica/exchange/ModifyExchangeServer.java b/src/main/java/top/leavesmc/leaves/protocol/syncmatica/exchange/ModifyExchangeServer.java -new file mode 100644 -index 0000000000000000000000000000000000000000..d87602fa78a8e599b71556f3dd103ff71ee83ae0 ---- /dev/null -+++ b/src/main/java/top/leavesmc/leaves/protocol/syncmatica/exchange/ModifyExchangeServer.java -@@ -0,0 +1,81 @@ -+package top.leavesmc.leaves.protocol.syncmatica.exchange; -+ -+import io.netty.buffer.Unpooled; -+import net.minecraft.network.FriendlyByteBuf; -+import net.minecraft.resources.ResourceLocation; -+import org.jetbrains.annotations.NotNull; -+import top.leavesmc.leaves.protocol.syncmatica.PacketType; -+import top.leavesmc.leaves.protocol.syncmatica.PlayerIdentifier; -+import top.leavesmc.leaves.protocol.syncmatica.ServerPlacement; -+import top.leavesmc.leaves.protocol.syncmatica.SyncmaticaProtocol; -+ -+import java.util.UUID; -+ -+public class ModifyExchangeServer extends AbstractExchange { -+ -+ private final ServerPlacement placement; -+ UUID placementId; -+ -+ public ModifyExchangeServer(final UUID placeId, final ExchangeTarget partner) { -+ super(partner); -+ placementId = placeId; -+ placement = SyncmaticaProtocol.getSyncmaticManager().getPlacement(placementId); -+ } -+ -+ @Override -+ public boolean checkPacket(final @NotNull ResourceLocation id, final FriendlyByteBuf packetBuf) { -+ return id.equals(PacketType.MODIFY_FINISH.identifier) && checkUUID(packetBuf, placement.getId()); -+ } -+ -+ @Override -+ public void handle(final @NotNull ResourceLocation id, final @NotNull FriendlyByteBuf packetBuf) { -+ packetBuf.readUUID(); -+ if (id.equals(PacketType.MODIFY_FINISH.identifier)) { -+ SyncmaticaProtocol.getCommunicationManager().receivePositionData(placement, packetBuf, getPartner()); -+ final PlayerIdentifier identifier = SyncmaticaProtocol.getPlayerIdentifierProvider().createOrGet( -+ getPartner() -+ ); -+ placement.setLastModifiedBy(identifier); -+ SyncmaticaProtocol.getSyncmaticManager().updateServerPlacement(); -+ succeed(); -+ } -+ } -+ -+ @Override -+ public void init() { -+ if (getPlacement() == null || SyncmaticaProtocol.getCommunicationManager().getModifier(placement) != null) { -+ close(true); -+ } else { -+ if (SyncmaticaProtocol.getPlayerIdentifierProvider().createOrGet(this.getPartner()).uuid.equals(placement.getOwner().uuid)) { -+ accept(); -+ } else { -+ close(true); -+ } -+ } -+ } -+ -+ private void accept() { -+ final FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer()); -+ buf.writeUUID(placement.getId()); -+ getPartner().sendPacket(PacketType.MODIFY_REQUEST_ACCEPT.identifier, buf); -+ SyncmaticaProtocol.getCommunicationManager().setModifier(placement, this); -+ } -+ -+ @Override -+ protected void sendCancelPacket() { -+ final FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer()); -+ buf.writeUUID(placementId); -+ getPartner().sendPacket(PacketType.MODIFY_REQUEST_DENY.identifier, buf); -+ } -+ -+ public ServerPlacement getPlacement() { -+ return placement; -+ } -+ -+ @Override -+ protected void onClose() { -+ if (SyncmaticaProtocol.getCommunicationManager().getModifier(placement) == this) { -+ SyncmaticaProtocol.getCommunicationManager().setModifier(placement, null); -+ } -+ } -+} -diff --git a/src/main/java/top/leavesmc/leaves/protocol/syncmatica/exchange/UploadExchange.java b/src/main/java/top/leavesmc/leaves/protocol/syncmatica/exchange/UploadExchange.java -new file mode 100644 -index 0000000000000000000000000000000000000000..9a1b37c69a3946b8f042a1118bf7dcf6ff0967ae ---- /dev/null -+++ b/src/main/java/top/leavesmc/leaves/protocol/syncmatica/exchange/UploadExchange.java -@@ -0,0 +1,101 @@ -+package top.leavesmc.leaves.protocol.syncmatica.exchange; -+ -+import io.netty.buffer.Unpooled; -+import net.minecraft.network.FriendlyByteBuf; -+import net.minecraft.resources.ResourceLocation; -+import org.jetbrains.annotations.NotNull; -+import top.leavesmc.leaves.protocol.syncmatica.PacketType; -+import top.leavesmc.leaves.protocol.syncmatica.ServerPlacement; -+ -+import java.io.File; -+import java.io.FileInputStream; -+import java.io.FileNotFoundException; -+import java.io.IOException; -+import java.io.InputStream; -+ -+public class UploadExchange extends AbstractExchange { -+ -+ private static final int BUFFER_SIZE = 16384; -+ -+ private final ServerPlacement toUpload; -+ private final InputStream inputStream; -+ private final byte[] buffer = new byte[BUFFER_SIZE]; -+ -+ public UploadExchange(final ServerPlacement syncmatic, final File uploadFile, final ExchangeTarget partner) throws FileNotFoundException { -+ super(partner); -+ toUpload = syncmatic; -+ inputStream = new FileInputStream(uploadFile); -+ } -+ -+ @Override -+ public boolean checkPacket(final @NotNull ResourceLocation id, final FriendlyByteBuf packetBuf) { -+ if (id.equals(PacketType.RECEIVED_LITEMATIC.identifier) -+ || id.equals(PacketType.CANCEL_LITEMATIC.identifier)) { -+ return checkUUID(packetBuf, toUpload.getId()); -+ } -+ return false; -+ } -+ -+ @Override -+ public void handle(final @NotNull ResourceLocation id, final @NotNull FriendlyByteBuf packetBuf) { -+ packetBuf.readUUID(); -+ if (id.equals(PacketType.RECEIVED_LITEMATIC.identifier)) { -+ send(); -+ } -+ if (id.equals(PacketType.CANCEL_LITEMATIC.identifier)) { -+ close(false); -+ } -+ } -+ -+ private void send() { -+ final int bytesRead; -+ try { -+ bytesRead = inputStream.read(buffer); -+ } catch (final IOException e) { -+ close(true); -+ e.printStackTrace(); -+ return; -+ } -+ if (bytesRead == -1) { -+ sendFinish(); -+ } else { -+ sendData(bytesRead); -+ } -+ } -+ -+ private void sendData(final int bytesRead) { -+ final FriendlyByteBuf FriendlyByteBuf = new FriendlyByteBuf(Unpooled.buffer()); -+ FriendlyByteBuf.writeUUID(toUpload.getId()); -+ FriendlyByteBuf.writeInt(bytesRead); -+ FriendlyByteBuf.writeBytes(buffer, 0, bytesRead); -+ getPartner().sendPacket(PacketType.SEND_LITEMATIC.identifier, FriendlyByteBuf); -+ } -+ -+ private void sendFinish() { -+ final FriendlyByteBuf FriendlyByteBuf = new FriendlyByteBuf(Unpooled.buffer()); -+ FriendlyByteBuf.writeUUID(toUpload.getId()); -+ getPartner().sendPacket(PacketType.FINISHED_LITEMATIC.identifier, FriendlyByteBuf); -+ succeed(); -+ } -+ -+ @Override -+ public void init() { -+ send(); -+ } -+ -+ @Override -+ protected void onClose() { -+ try { -+ inputStream.close(); -+ } catch (final IOException e) { -+ e.printStackTrace(); -+ } -+ } -+ -+ @Override -+ protected void sendCancelPacket() { -+ final FriendlyByteBuf FriendlyByteBuf = new FriendlyByteBuf(Unpooled.buffer()); -+ FriendlyByteBuf.writeUUID(toUpload.getId()); -+ getPartner().sendPacket(PacketType.CANCEL_LITEMATIC.identifier, FriendlyByteBuf); -+ } -+} -diff --git a/src/main/java/top/leavesmc/leaves/protocol/syncmatica/exchange/VersionHandshakeServer.java b/src/main/java/top/leavesmc/leaves/protocol/syncmatica/exchange/VersionHandshakeServer.java -new file mode 100644 -index 0000000000000000000000000000000000000000..448d5e8423347c0154a146906617e32e18fbc30f ---- /dev/null -+++ b/src/main/java/top/leavesmc/leaves/protocol/syncmatica/exchange/VersionHandshakeServer.java -@@ -0,0 +1,65 @@ -+package top.leavesmc.leaves.protocol.syncmatica.exchange; -+ -+import io.netty.buffer.Unpooled; -+import net.minecraft.network.FriendlyByteBuf; -+import net.minecraft.resources.ResourceLocation; -+import org.jetbrains.annotations.NotNull; -+import top.leavesmc.leaves.protocol.syncmatica.FeatureSet; -+import top.leavesmc.leaves.protocol.syncmatica.PacketType; -+import top.leavesmc.leaves.protocol.syncmatica.ServerPlacement; -+import top.leavesmc.leaves.protocol.syncmatica.SyncmaticaProtocol; -+ -+import java.util.Collection; -+ -+public class VersionHandshakeServer extends FeatureExchange { -+ -+ public VersionHandshakeServer(final ExchangeTarget partner) { -+ super(partner); -+ } -+ -+ @Override -+ public boolean checkPacket(final @NotNull ResourceLocation id, final FriendlyByteBuf packetBuf) { -+ return id.equals(PacketType.REGISTER_VERSION.identifier) -+ || super.checkPacket(id, packetBuf); -+ } -+ -+ @Override -+ public void handle(final @NotNull ResourceLocation id, final FriendlyByteBuf packetBuf) { -+ if (id.equals(PacketType.REGISTER_VERSION.identifier)) { -+ String partnerVersion = packetBuf.readUtf(32767); -+ if (partnerVersion.equals("0.0.1")) { -+ close(false); -+ return; -+ } -+ final FeatureSet fs = FeatureSet.fromVersionString(partnerVersion); -+ if (fs == null) { -+ requestFeatureSet(); -+ } else { -+ getPartner().setFeatureSet(fs); -+ onFeatureSetReceive(); -+ } -+ } else { -+ super.handle(id, packetBuf); -+ } -+ -+ } -+ -+ @Override -+ public void onFeatureSetReceive() { -+ final FriendlyByteBuf newBuf = new FriendlyByteBuf(Unpooled.buffer()); -+ final Collection l = SyncmaticaProtocol.getSyncmaticManager().getAll(); -+ newBuf.writeInt(l.size()); -+ for (final ServerPlacement p : l) { -+ getManager().putMetaData(p, newBuf, getPartner()); -+ } -+ getPartner().sendPacket(PacketType.CONFIRM_USER.identifier, newBuf); -+ succeed(); -+ } -+ -+ @Override -+ public void init() { -+ final FriendlyByteBuf newBuf = new FriendlyByteBuf(Unpooled.buffer()); -+ newBuf.writeUtf(SyncmaticaProtocol.PROTOCOL_VERSION); -+ getPartner().sendPacket(PacketType.REGISTER_VERSION.identifier, newBuf); -+ } -+} diff --git a/patches/server/0057-Disable-distance-check-for-UseItemOnPacket.patch b/patches/server/0057-Disable-distance-check-for-UseItemOnPacket.patch index 231808b5..2a086fc3 100644 --- a/patches/server/0057-Disable-distance-check-for-UseItemOnPacket.patch +++ b/patches/server/0057-Disable-distance-check-for-UseItemOnPacket.patch @@ -5,15 +5,15 @@ Subject: [PATCH] Disable distance check for UseItemOnPacket diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 1ff216875a540452b0ce3e5c462a5738c35a3f98..8f2bb7df5c0925b2ef700aebfcbdc539a4dddfe1 100644 +index 43323f522c8c896bd66cb0fd9c1c5972e99188c0..9c9b702948b5a361a4cae0a177b1b298e202d64e 100644 --- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -1932,7 +1932,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl - Vec3 vec3d2 = vec3d.subtract(vec3d1); +@@ -1920,7 +1920,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + Vec3 vec3d1 = vec3d.subtract(Vec3.atCenterOf(blockposition)); double d0 = 1.0000001D; -- if (Math.abs(vec3d2.x()) < 1.0000001D && Math.abs(vec3d2.y()) < 1.0000001D && Math.abs(vec3d2.z()) < 1.0000001D) { -+ if (top.leavesmc.leaves.LeavesConfig.disableDistanceCheckForUseItem || (Math.abs(vec3d2.x()) < 1.0000001D && Math.abs(vec3d2.y()) < 1.0000001D && Math.abs(vec3d2.z()) < 1.0000001D)) { // Leaves - disable check +- if (Math.abs(vec3d1.x()) < 1.0000001D && Math.abs(vec3d1.y()) < 1.0000001D && Math.abs(vec3d1.z()) < 1.0000001D) { ++ if (top.leavesmc.leaves.LeavesConfig.disableDistanceCheckForUseItem || (Math.abs(vec3d1.x()) < 1.0000001D && Math.abs(vec3d1.y()) < 1.0000001D && Math.abs(vec3d1.z()) < 1.0000001D)) { Direction enumdirection = movingobjectpositionblock.getDirection(); this.player.resetLastActionTime(); diff --git a/patches/server/0058-No-feather-falling-trample.patch b/patches/server/0058-No-feather-falling-trample.patch index 3ff65eed..047307aa 100644 --- a/patches/server/0058-No-feather-falling-trample.patch +++ b/patches/server/0058-No-feather-falling-trample.patch @@ -5,10 +5,10 @@ Subject: [PATCH] No feather falling trample diff --git a/src/main/java/net/minecraft/world/level/block/FarmBlock.java b/src/main/java/net/minecraft/world/level/block/FarmBlock.java -index 6e4c852c93f2418ea69e485ed3a10cbe3a6e3bd2..4630cb593cd493c5105c53e70d099f3aa1dd212f 100644 +index d59e33e7326489c6d55d316d0130f22235f4c63c..a454b8e6ef9133fbbae168bd4748c90ecd9fea37 100644 --- a/src/main/java/net/minecraft/world/level/block/FarmBlock.java +++ b/src/main/java/net/minecraft/world/level/block/FarmBlock.java -@@ -111,6 +111,13 @@ public class FarmBlock extends Block { +@@ -112,6 +112,13 @@ public class FarmBlock extends Block { public void fallOn(Level world, BlockState state, BlockPos pos, Entity entity, float fallDistance) { super.fallOn(world, state, pos, entity, fallDistance); // CraftBukkit - moved here as game rules / events shouldn't affect fall damage. if (!world.isClientSide && world.random.nextFloat() < fallDistance - 0.5F && entity instanceof LivingEntity && (entity instanceof Player || world.getGameRules().getBoolean(GameRules.RULE_MOBGRIEFING)) && entity.getBbWidth() * entity.getBbWidth() * entity.getBbHeight() > 0.512F) { diff --git a/patches/server/0060-Redstone-wire-dont-connect-if-on-trapdoor.patch b/patches/server/0060-Redstone-wire-dont-connect-if-on-trapdoor.patch index 531a0eb8..1bfa6b45 100644 --- a/patches/server/0060-Redstone-wire-dont-connect-if-on-trapdoor.patch +++ b/patches/server/0060-Redstone-wire-dont-connect-if-on-trapdoor.patch @@ -5,19 +5,19 @@ Subject: [PATCH] Redstone wire dont connect if on trapdoor diff --git a/src/main/java/net/minecraft/world/level/block/RedStoneWireBlock.java b/src/main/java/net/minecraft/world/level/block/RedStoneWireBlock.java -index b5a71fd4e2f55bf036c2c697da5d50cc90fc657c..6afe31169948d644f9af68b8eb31a89c53304826 100644 +index c131734cad123a35456d18f8a161f77a4ac9ac99..e5ca4c86bdd2f86eb0427e6435408c1439cd5a09 100644 --- a/src/main/java/net/minecraft/world/level/block/RedStoneWireBlock.java +++ b/src/main/java/net/minecraft/world/level/block/RedStoneWireBlock.java -@@ -174,7 +174,7 @@ public class RedStoneWireBlock extends Block { +@@ -173,7 +173,7 @@ public class RedStoneWireBlock extends Block { @Override - public BlockState updateShape(BlockState state, Direction direction, BlockState neighborState, LevelAccessor world, BlockPos pos, BlockPos neighborPos) { + protected BlockState updateShape(BlockState state, Direction direction, BlockState neighborState, LevelAccessor world, BlockPos pos, BlockPos neighborPos) { if (direction == Direction.DOWN) { - return !this.canSurviveOn(world, neighborPos, neighborState) ? Blocks.AIR.defaultBlockState() : state; + return top.leavesmc.leaves.LeavesConfig.redstoneDontCantOnTrapDoor ? state : !this.canSurviveOn(world, neighborPos, neighborState) ? Blocks.AIR.defaultBlockState() : state; // Leaves - behavior to 1.19 } else if (direction == Direction.UP) { return this.getConnectionState(world, state, pos); } else { -@@ -233,7 +233,7 @@ public class RedStoneWireBlock extends Block { +@@ -232,7 +232,7 @@ public class RedStoneWireBlock extends Block { BlockState iblockdata = world.getBlockState(blockposition1); if (flag) { diff --git a/patches/server/0061-Disable-check-out-of-order-command.patch b/patches/server/0061-Disable-check-out-of-order-command.patch index 444a6d6d..a246a718 100644 --- a/patches/server/0061-Disable-check-out-of-order-command.patch +++ b/patches/server/0061-Disable-check-out-of-order-command.patch @@ -5,15 +5,19 @@ Subject: [PATCH] Disable check out-of-order command diff --git a/src/main/java/net/minecraft/network/chat/SignedMessageChain.java b/src/main/java/net/minecraft/network/chat/SignedMessageChain.java -index 0af9ed92824ccf30814eceb6a2c2e5c12661c991..b007bacf311edd71ef7773e43e8aa2ddd4871bd4 100644 +index 300929a406905f5ff1ede664d5b99fb0938d4d2e..9756e8b560281796081a6ee157f9d92240bd2d56 100644 --- a/src/main/java/net/minecraft/network/chat/SignedMessageChain.java +++ b/src/main/java/net/minecraft/network/chat/SignedMessageChain.java -@@ -37,7 +37,7 @@ public class SignedMessageChain { - throw new SignedMessageChain.DecodeException(Component.translatable("chat.disabled.chain_broken"), false); // Paper - diff on change (if disconnects, need a new kick event cause) - } else if (playerPublicKey.data().hasExpired()) { - throw new SignedMessageChain.DecodeException(Component.translatable("chat.disabled.expiredProfileKey"), false, org.bukkit.event.player.PlayerKickEvent.Cause.EXPIRED_PROFILE_PUBLIC_KEY); // Paper - kick event causes -- } else if (body.timeStamp().isBefore(this.lastTimeStamp)) { -+ } else if (body.timeStamp().isBefore(this.lastTimeStamp) && !top.leavesmc.leaves.LeavesConfig.disableCheckOutOfOrderCommand) { // Leaves - disable check to support Velocity - throw new SignedMessageChain.DecodeException(Component.translatable("multiplayer.disconnect.out_of_order_chat"), true, org.bukkit.event.player.PlayerKickEvent.Cause.OUT_OF_ORDER_CHAT); // Paper - kick event causes - } else { - this.lastTimeStamp = body.timeStamp(); +@@ -46,8 +46,10 @@ public class SignedMessageChain { + if (signedMessageLink == null) { + throw new SignedMessageChain.DecodeException(SignedMessageChain.DecodeException.CHAIN_BROKEN); + } else if (body.timeStamp().isBefore(SignedMessageChain.this.lastTimeStamp)) { +- this.setChainBroken(); +- throw new SignedMessageChain.DecodeException(SignedMessageChain.DecodeException.OUT_OF_ORDER_CHAT, org.bukkit.event.player.PlayerKickEvent.Cause.OUT_OF_ORDER_CHAT); // Paper - kick event causes ++ if (!top.leavesmc.leaves.LeavesConfig.disableCheckOutOfOrderCommand) { // Leaves - disable check to support Velocity ++ this.setChainBroken(); ++ throw new SignedMessageChain.DecodeException(SignedMessageChain.DecodeException.OUT_OF_ORDER_CHAT, org.bukkit.event.player.PlayerKickEvent.Cause.OUT_OF_ORDER_CHAT); // Paper - kick event causes ++ } + } else { + SignedMessageChain.this.lastTimeStamp = body.timeStamp(); + PlayerChatMessage playerChatMessage = new PlayerChatMessage(signedMessageLink, signature, body, null, FilterMask.PASS_THROUGH); diff --git a/patches/server/0062-Despawn-enderman-with-block.patch b/patches/server/0062-Despawn-enderman-with-block.patch index 74826caf..5c36a7b2 100644 --- a/patches/server/0062-Despawn-enderman-with-block.patch +++ b/patches/server/0062-Despawn-enderman-with-block.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Despawn enderman with block diff --git a/src/main/java/net/minecraft/world/entity/monster/EnderMan.java b/src/main/java/net/minecraft/world/entity/monster/EnderMan.java -index 9ea374d9baf1612898b67f329e6b5c798ad43b3e..5afa37d2026833c1ded968f957c522436dfe43fd 100644 +index d95be55d0a76ef5baa7f70ccade5f641a65c02c9..adffea4854c84a974b98ce8e3069725bd69672ab 100644 --- a/src/main/java/net/minecraft/world/entity/monster/EnderMan.java +++ b/src/main/java/net/minecraft/world/entity/monster/EnderMan.java -@@ -486,7 +486,7 @@ public class EnderMan extends Monster implements NeutralMob { +@@ -469,7 +469,7 @@ public class EnderMan extends Monster implements NeutralMob { @Override public boolean requiresCustomPersistence() { diff --git a/patches/server/0063-Placeholder-for-Leaves-carpet-support.patch b/patches/server/0063-Placeholder-for-Leaves-carpet-support.patch new file mode 100644 index 00000000..d692acba --- /dev/null +++ b/patches/server/0063-Placeholder-for-Leaves-carpet-support.patch @@ -0,0 +1,15 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: MC_XiaoHei +Date: Wed, 8 May 2024 22:27:22 +0800 +Subject: [PATCH] Placeholder for Leaves-carpet-support + + +diff --git a/.gitignore b/.gitignore +index 1ad93453221244f880855b510e888e759275b640..3811c0d849a3eb028ed1a6b7a2d4747f7f570448 100644 +--- a/.gitignore ++++ b/.gitignore +@@ -46,4 +46,3 @@ dependency-reduced-pom.xml + # vs code + /.vscode + /.factorypath +- diff --git a/patches/server/0064-Creative-fly-no-clip.patch b/patches/server/0064-Creative-fly-no-clip.patch index f354c577..53b5e59c 100644 --- a/patches/server/0064-Creative-fly-no-clip.patch +++ b/patches/server/0064-Creative-fly-no-clip.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Creative fly no clip diff --git a/src/main/java/net/minecraft/world/entity/player/Player.java b/src/main/java/net/minecraft/world/entity/player/Player.java -index 567704f61034363e48ef2a5b5566ebdc91682297..8f37983e57321da2a59e047be4a0119118a85a6e 100644 +index 093d1388ff90ad59110a37536b6639f939549068..09d1aa016a3ecacf00174c574b010622b955676f 100644 --- a/src/main/java/net/minecraft/world/entity/player/Player.java +++ b/src/main/java/net/minecraft/world/entity/player/Player.java -@@ -238,8 +238,8 @@ public abstract class Player extends LivingEntity { +@@ -250,8 +250,8 @@ public abstract class Player extends LivingEntity { @Override public void tick() { @@ -19,7 +19,7 @@ index 567704f61034363e48ef2a5b5566ebdc91682297..8f37983e57321da2a59e047be4a01191 this.setOnGround(false); } -@@ -418,7 +418,7 @@ public abstract class Player extends LivingEntity { +@@ -430,7 +430,7 @@ public abstract class Player extends LivingEntity { Pose entitypose1; @@ -28,7 +28,7 @@ index 567704f61034363e48ef2a5b5566ebdc91682297..8f37983e57321da2a59e047be4a01191 if (this.canPlayerFitWithinBlocksAndEntitiesWhen(Pose.CROUCHING)) { entitypose1 = Pose.CROUCHING; } else { -@@ -578,7 +578,7 @@ public abstract class Player extends LivingEntity { +@@ -577,7 +577,7 @@ public abstract class Player extends LivingEntity { } this.bob += (f - this.bob) * 0.4F; @@ -37,7 +37,7 @@ index 567704f61034363e48ef2a5b5566ebdc91682297..8f37983e57321da2a59e047be4a01191 AABB axisalignedbb; if (this.isPassenger() && !this.getVehicle().isRemoved()) { -@@ -2076,6 +2076,21 @@ public abstract class Player extends LivingEntity { +@@ -2099,6 +2099,21 @@ public abstract class Player extends LivingEntity { @Override public abstract boolean isSpectator(); @@ -60,10 +60,10 @@ index 567704f61034363e48ef2a5b5566ebdc91682297..8f37983e57321da2a59e047be4a01191 public boolean canBeHitByProjectile() { return !this.isSpectator() && super.canBeHitByProjectile(); diff --git a/src/main/java/net/minecraft/world/item/BlockItem.java b/src/main/java/net/minecraft/world/item/BlockItem.java -index e1ffc1b52641e9cf91f524a83b8c8ee047932d1a..2c799cd93fbba079f53dd37535126a6c0e0261b7 100644 +index 59da4a6f4ebd94a798560b86089e544ff8f1909d..b0fb7c0f880f9636e2103bb3369cb3d06a72daa0 100644 --- a/src/main/java/net/minecraft/world/item/BlockItem.java +++ b/src/main/java/net/minecraft/world/item/BlockItem.java -@@ -228,7 +228,7 @@ public class BlockItem extends Item { +@@ -205,7 +205,7 @@ public class BlockItem extends Item { CollisionContext voxelshapecollision = entityhuman == null ? CollisionContext.empty() : CollisionContext.of(entityhuman); // CraftBukkit start - store default return Level world = context.getLevel(); // Paper - Cancel hit for vanished players @@ -73,7 +73,7 @@ index e1ffc1b52641e9cf91f524a83b8c8ee047932d1a..2c799cd93fbba079f53dd37535126a6c BlockCanBuildEvent event = new BlockCanBuildEvent(CraftBlock.at(context.getLevel(), context.getClickedPos()), player, CraftBlockData.fromData(state), defaultReturn, org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(context.getHand())); // Paper - Expose hand in BlockCanBuildEvent diff --git a/src/main/java/net/minecraft/world/item/StandingAndWallBlockItem.java b/src/main/java/net/minecraft/world/item/StandingAndWallBlockItem.java -index 0b761f3ae15ad4a3b8152f497a60403212109534..fdb2bb8a5e6c5d69692804adb086a2d476d558cd 100644 +index f8f909ebdad5e96379e8bd8c610164ef0697368e..e484d2f73f1fd32be72fda5b8c4e710d9b621652 100644 --- a/src/main/java/net/minecraft/world/item/StandingAndWallBlockItem.java +++ b/src/main/java/net/minecraft/world/item/StandingAndWallBlockItem.java @@ -56,7 +56,7 @@ public class StandingAndWallBlockItem extends BlockItem { @@ -86,10 +86,10 @@ index 0b761f3ae15ad4a3b8152f497a60403212109534..fdb2bb8a5e6c5d69692804adb086a2d4 BlockCanBuildEvent event = new BlockCanBuildEvent(CraftBlock.at(world, blockposition), player, CraftBlockData.fromData(iblockdata1), defaultReturn, org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(context.getHand())); // Paper - Expose hand in BlockCanBuildEvent diff --git a/src/main/java/net/minecraft/world/level/block/entity/ShulkerBoxBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/ShulkerBoxBlockEntity.java -index 5c311270a39f6b4996c8b58822d24556c67adc41..cb40179236952edcc1678a58a5886b1916349314 100644 +index 0d68db20f5fbe5e834f12c1e8fd429099a44e4b6..193673d2b70035dac69422aaae185315a901effc 100644 --- a/src/main/java/net/minecraft/world/level/block/entity/ShulkerBoxBlockEntity.java +++ b/src/main/java/net/minecraft/world/level/block/entity/ShulkerBoxBlockEntity.java -@@ -162,7 +162,7 @@ public class ShulkerBoxBlockEntity extends RandomizableContainerBlockEntity impl +@@ -163,7 +163,7 @@ public class ShulkerBoxBlockEntity extends RandomizableContainerBlockEntity impl while (iterator.hasNext()) { Entity entity = (Entity) iterator.next(); @@ -99,10 +99,10 @@ index 5c311270a39f6b4996c8b58822d24556c67adc41..cb40179236952edcc1678a58a5886b19 } } diff --git a/src/main/java/net/minecraft/world/level/block/entity/TheEndGatewayBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/TheEndGatewayBlockEntity.java -index 098fde8200a11f91f934ddab6b1486dac4014dfe..88c9097368f7a842e1d31c460330aa3d267676ef 100644 +index 93bd70c1dc2ba8b893a6087730071c81fb1132f4..4af071ff2d0a29ad9315e1076436f70f848c89b8 100644 --- a/src/main/java/net/minecraft/world/level/block/entity/TheEndGatewayBlockEntity.java +++ b/src/main/java/net/minecraft/world/level/block/entity/TheEndGatewayBlockEntity.java -@@ -18,6 +18,7 @@ import net.minecraft.util.Mth; +@@ -19,6 +19,7 @@ import net.minecraft.util.Mth; import net.minecraft.util.RandomSource; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.EntitySelector; @@ -110,7 +110,7 @@ index 098fde8200a11f91f934ddab6b1486dac4014dfe..88c9097368f7a842e1d31c460330aa3d import net.minecraft.world.entity.projectile.ThrownEnderpearl; import net.minecraft.world.level.BlockGetter; import net.minecraft.world.level.ChunkPos; -@@ -121,7 +122,7 @@ public class TheEndGatewayBlockEntity extends TheEndPortalBlockEntity { +@@ -117,7 +118,7 @@ public class TheEndGatewayBlockEntity extends TheEndPortalBlockEntity { } public static boolean canEntityTeleport(Entity entity) { @@ -120,10 +120,10 @@ index 098fde8200a11f91f934ddab6b1486dac4014dfe..88c9097368f7a842e1d31c460330aa3d public boolean isSpawning() { diff --git a/src/main/java/net/minecraft/world/level/block/piston/PistonMovingBlockEntity.java b/src/main/java/net/minecraft/world/level/block/piston/PistonMovingBlockEntity.java -index 9f45dda6ff45ac1ffb7ac99575b7d09bdc61c56a..1fde81b6f68f43cd8d9c6123fff1262ccbaa6038 100644 +index b35f476e26a020cf75e53a5eb488717d996a6935..73651a8ae9341807ec96300914d49329fb8a4e90 100644 --- a/src/main/java/net/minecraft/world/level/block/piston/PistonMovingBlockEntity.java +++ b/src/main/java/net/minecraft/world/level/block/piston/PistonMovingBlockEntity.java -@@ -146,7 +146,7 @@ public class PistonMovingBlockEntity extends BlockEntity { +@@ -147,7 +147,7 @@ public class PistonMovingBlockEntity extends BlockEntity { h = (double)direction.getStepZ(); } @@ -132,7 +132,7 @@ index 9f45dda6ff45ac1ffb7ac99575b7d09bdc61c56a..1fde81b6f68f43cd8d9c6123fff1262c // Paper - EAR items stuck in in slime pushed by a piston entity.activatedTick = Math.max(entity.activatedTick, net.minecraft.server.MinecraftServer.currentTick + 10); entity.activatedImmunityTick = Math.max(entity.activatedImmunityTick, net.minecraft.server.MinecraftServer.currentTick + 10); -@@ -182,6 +182,7 @@ public class PistonMovingBlockEntity extends BlockEntity { +@@ -183,6 +183,7 @@ public class PistonMovingBlockEntity extends BlockEntity { } private static void moveEntityByPiston(Direction direction, Entity entity, double distance, Direction movementDirection) { diff --git a/patches/server/0065-Optimized-dragon-respawn.patch b/patches/server/0065-Optimized-dragon-respawn.patch index e956aefd..b8c5709c 100644 --- a/patches/server/0065-Optimized-dragon-respawn.patch +++ b/patches/server/0065-Optimized-dragon-respawn.patch @@ -18,7 +18,7 @@ index ee99519ebd46b1db3e76e7eb86e5cc121c867dc4..63f6f1328c4e39cc1f35480166ae5e22 for (int j = 0; j < this.height; j++) { for (int k = 0; k < this.depth; k++) { diff --git a/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java b/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java -index d4f903c402765c6e8e1db99e148613748f530726..939f1d6958c93819e188946c2173c2668f6d90a1 100644 +index 18a1b4325cac81b040596071dab99ef9bf6f3142..359abfc7045b1a1c470d795c803fd357978aacd1 100644 --- a/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java +++ b/src/main/java/net/minecraft/world/level/dimension/end/EndDragonFight.java @@ -45,6 +45,7 @@ import net.minecraft.world.entity.boss.enderdragon.phases.EnderDragonPhase; diff --git a/patches/server/0066-Enchantment-mending-compatibility-infinity.patch b/patches/server/0066-Enchantment-mending-compatibility-infinity.patch index 23b03f1a..9ed28600 100644 --- a/patches/server/0066-Enchantment-mending-compatibility-infinity.patch +++ b/patches/server/0066-Enchantment-mending-compatibility-infinity.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Enchantment mending compatibility infinity diff --git a/src/main/java/net/minecraft/world/item/enchantment/ArrowInfiniteEnchantment.java b/src/main/java/net/minecraft/world/item/enchantment/ArrowInfiniteEnchantment.java -index 04c39359585d909dedbdfd78f6cbdc06b926607a..aef23527fe185df4929dffb96d8e705910bcabe0 100644 +index 81cc05c929d612898609965d82454b89cd18f9f5..26e4cdeed1f97207bff3da6967c350904e7f1d4f 100644 --- a/src/main/java/net/minecraft/world/item/enchantment/ArrowInfiniteEnchantment.java +++ b/src/main/java/net/minecraft/world/item/enchantment/ArrowInfiniteEnchantment.java -@@ -19,6 +19,6 @@ public class ArrowInfiniteEnchantment extends Enchantment { +@@ -7,6 +7,6 @@ public class ArrowInfiniteEnchantment extends Enchantment { @Override public boolean checkCompatibility(Enchantment other) { diff --git a/patches/server/0067-Shave-snow-layers.patch b/patches/server/0067-Shave-snow-layers.patch index 638b5e16..cb635e68 100644 --- a/patches/server/0067-Shave-snow-layers.patch +++ b/patches/server/0067-Shave-snow-layers.patch @@ -5,11 +5,11 @@ Subject: [PATCH] Shave snow layers diff --git a/src/main/java/net/minecraft/world/item/ShovelItem.java b/src/main/java/net/minecraft/world/item/ShovelItem.java -index 9aba0211f37501bbd19b583d22fa83eae32390d9..8e496f0256091a2ad82537c76b1ce7125dc5a0da 100644 +index 24f6a158e4759aac3be8da4cf5e0d40bd295355b..59c6c9b4d574b2c1f6c54a116c4bab43b71da7f8 100644 --- a/src/main/java/net/minecraft/world/item/ShovelItem.java +++ b/src/main/java/net/minecraft/world/item/ShovelItem.java -@@ -11,10 +11,12 @@ import net.minecraft.tags.BlockTags; - import net.minecraft.world.InteractionResult; +@@ -12,10 +12,12 @@ import net.minecraft.world.InteractionResult; + import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.context.UseOnContext; +import net.minecraft.world.item.enchantment.EnchantmentHelper; @@ -21,7 +21,7 @@ index 9aba0211f37501bbd19b583d22fa83eae32390d9..8e496f0256091a2ad82537c76b1ce712 import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.gameevent.GameEvent; -@@ -43,6 +45,22 @@ public class ShovelItem extends DiggerItem { +@@ -44,6 +46,22 @@ public class ShovelItem extends DiggerItem { return InteractionResult.PASS; } else { Player player = context.getPlayer(); diff --git a/patches/server/0068-Spawn-ignore-lc.patch b/patches/server/0068-Spawn-ignore-lc.patch index fb82b883..7a8373aa 100644 --- a/patches/server/0068-Spawn-ignore-lc.patch +++ b/patches/server/0068-Spawn-ignore-lc.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Spawn ignore lc diff --git a/src/main/java/net/minecraft/world/level/NaturalSpawner.java b/src/main/java/net/minecraft/world/level/NaturalSpawner.java -index 667a480b7c546bb130bbd32a8ae7ce85d37a6b0a..3e10668ca548472238bb4bb0a80a89f169a71496 100644 +index 50879a522a1c9fa817ca226792793c1c3c3e1940..ccd6cdf1d62d0c416fed94547575765f4ec37af5 100644 --- a/src/main/java/net/minecraft/world/level/NaturalSpawner.java +++ b/src/main/java/net/minecraft/world/level/NaturalSpawner.java -@@ -40,6 +40,7 @@ import net.minecraft.world.level.block.state.BlockState; +@@ -38,6 +38,7 @@ import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.chunk.ChunkAccess; import net.minecraft.world.level.chunk.ChunkGenerator; import net.minecraft.world.level.chunk.LevelChunk; @@ -16,7 +16,7 @@ index 667a480b7c546bb130bbd32a8ae7ce85d37a6b0a..3e10668ca548472238bb4bb0a80a89f1 import net.minecraft.world.level.levelgen.Heightmap; import net.minecraft.world.level.levelgen.structure.BuiltinStructures; import net.minecraft.world.level.levelgen.structure.Structure; -@@ -204,6 +205,19 @@ public final class NaturalSpawner { +@@ -199,6 +200,19 @@ public final class NaturalSpawner { } public static int spawnCategoryForChunk(MobCategory group, ServerLevel world, LevelChunk chunk, NaturalSpawner.SpawnPredicate checker, NaturalSpawner.AfterSpawnCallback runner, int maxSpawns, Consumer trackEntity) { // Paper end - Optional per player mob spawns @@ -36,7 +36,7 @@ index 667a480b7c546bb130bbd32a8ae7ce85d37a6b0a..3e10668ca548472238bb4bb0a80a89f1 BlockPos blockposition = NaturalSpawner.getRandomPosWithin(world, chunk); if (blockposition.getY() >= world.getMinBuildHeight() + 1) { -@@ -212,6 +226,16 @@ public final class NaturalSpawner { +@@ -207,6 +221,16 @@ public final class NaturalSpawner { return 0; // Paper - Optional per player mob spawns } diff --git a/patches/server/0069-Placeholder-for-Elytra-aeronautics-no-chunk-load.patch b/patches/server/0069-Placeholder-for-Elytra-aeronautics-no-chunk-load.patch new file mode 100644 index 00000000..010ac78d --- /dev/null +++ b/patches/server/0069-Placeholder-for-Elytra-aeronautics-no-chunk-load.patch @@ -0,0 +1,15 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: MC_XiaoHei +Date: Wed, 8 May 2024 22:28:03 +0800 +Subject: [PATCH] Placeholder for Elytra-aeronautics-no-chunk-load + + +diff --git a/.gitignore b/.gitignore +index 3811c0d849a3eb028ed1a6b7a2d4747f7f570448..1ad93453221244f880855b510e888e759275b640 100644 +--- a/.gitignore ++++ b/.gitignore +@@ -46,3 +46,4 @@ dependency-reduced-pom.xml + # vs code + /.vscode + /.factorypath ++ diff --git a/patches/server/0070-Cache-ignite-odds.patch b/patches/server/0070-Cache-ignite-odds.patch index bd3013c4..1688bc37 100644 --- a/patches/server/0070-Cache-ignite-odds.patch +++ b/patches/server/0070-Cache-ignite-odds.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Cache ignite odds diff --git a/src/main/java/net/minecraft/world/level/block/FireBlock.java b/src/main/java/net/minecraft/world/level/block/FireBlock.java -index 7f05e58358024d303eab9ab4fbc1bb299760ad1e..8670ffb3aec9980622ae7f59b407689790c075ae 100644 +index 4d467ecf0dd8006b9fa4fb8fb5aadc0622a1e512..588b50e9e3936ad3d4d5a2b5214ecb6e44b88dcb 100644 --- a/src/main/java/net/minecraft/world/level/block/FireBlock.java +++ b/src/main/java/net/minecraft/world/level/block/FireBlock.java @@ -220,6 +220,7 @@ public class FireBlock extends BaseFireBlock { diff --git a/patches/server/0071-Lava-riptide.patch b/patches/server/0071-Lava-riptide.patch index 1f0d9485..d6e8ed8c 100644 --- a/patches/server/0071-Lava-riptide.patch +++ b/patches/server/0071-Lava-riptide.patch @@ -5,19 +5,19 @@ Subject: [PATCH] Lava riptide diff --git a/src/main/java/net/minecraft/world/item/TridentItem.java b/src/main/java/net/minecraft/world/item/TridentItem.java -index a792c7b7a6179aa88fc473b27ef0ca13bd91a395..95318fe882bc65d1483c7547b41e6f660c0c0012 100644 +index 47de500fddb0716d142f8f5876a82a95afaa06fa..1e338ad96d2a21a40c5fac63a392bcb6e30c72f8 100644 --- a/src/main/java/net/minecraft/world/item/TridentItem.java +++ b/src/main/java/net/minecraft/world/item/TridentItem.java -@@ -66,7 +66,7 @@ public class TridentItem extends Item implements Vanishable { +@@ -70,7 +70,7 @@ public class TridentItem extends Item implements ProjectileItem { if (j >= 10) { int k = EnchantmentHelper.getRiptide(stack); - if (k <= 0 || entityhuman.isInWaterOrRain()) { + if (k <= 0 || entityhuman.isInWaterOrRain() || (top.leavesmc.leaves.LeavesConfig.lavaRiptide && entityhuman.isInLava())) { // Leaves - lava riptide if (!world.isClientSide) { - // CraftBukkit - moved down - /* -@@ -160,7 +160,7 @@ public class TridentItem extends Item implements Vanishable { + // itemstack.hurtAndBreak(1, entityhuman, EntityLiving.getSlotForHand(entityliving.getUsedItemHand())); // CraftBukkit - moved down + if (k == 0) { +@@ -155,7 +155,7 @@ public class TridentItem extends Item implements ProjectileItem { if (itemstack.getDamageValue() >= itemstack.getMaxDamage() - 1) { return InteractionResultHolder.fail(itemstack); diff --git a/patches/server/0072-No-block-update-command.patch b/patches/server/0072-No-block-update-command.patch index 571e6cbf..6a3b030d 100644 --- a/patches/server/0072-No-block-update-command.patch +++ b/patches/server/0072-No-block-update-command.patch @@ -5,10 +5,10 @@ Subject: [PATCH] No block update command diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index 9488fbbf0442d38c18632f0ed50c23fd27078f71..9e95045f10904744f7cbf6e4203f8cbe762a9afc 100644 +index 7cea0325cb626f5de5d8d58ecaa580e6d979f636..9fdbf663b3bfd51efb5a0efbc0f64eae29d8c7a6 100644 --- a/src/main/java/net/minecraft/server/level/ServerLevel.java +++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -2517,6 +2517,7 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -2464,6 +2464,7 @@ public class ServerLevel extends Level implements WorldGenLevel { @Override public void blockUpdated(BlockPos pos, Block block) { @@ -17,20 +17,20 @@ index 9488fbbf0442d38c18632f0ed50c23fd27078f71..9e95045f10904744f7cbf6e4203f8cbe // CraftBukkit start if (this.populating) { diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java -index 1ad126d992d95062a3db08374db7a927f23a0cac..418a7c9217e6e06bff6866deffff3e7822d35a14 100644 +index 893efb2c4a07c33d41e934279dd914a9dbd4ef79..9c2f9f4e2afb2bd4a94ae4dab54ef131466ed69d 100644 --- a/src/main/java/net/minecraft/world/item/ItemStack.java +++ b/src/main/java/net/minecraft/world/item/ItemStack.java -@@ -483,7 +483,7 @@ public final class ItemStack { +@@ -504,7 +504,7 @@ public final class ItemStack implements DataComponentHolder { net.minecraft.world.level.block.state.BlockState block = world.getBlockState(newblockposition); if (!(block.getBlock() instanceof BaseEntityBlock)) { // Containers get placed automatically -- block.getBlock().onPlace(block, world, newblockposition, oldBlock, true, context); // Paper - pass context -+ if (!top.leavesmc.leaves.command.NoBlockUpdateCommand.isNoBlockUpdate()) block.getBlock().onPlace(block, world, newblockposition, oldBlock, true, context); // Paper - pass context // Leaves - no block update +- block.onPlace(world, newblockposition, oldBlock, true, context); // Paper - pass context ++ if (!top.leavesmc.leaves.command.NoBlockUpdateCommand.isNoBlockUpdate()) block.onPlace(world, newblockposition, oldBlock, true, context); // Paper - pass context // Leaves - no block update } world.notifyAndUpdatePhysics(newblockposition, null, oldBlock, block, world.getBlockState(newblockposition), updateFlag, 512); // send null chunk as chunk.k() returns false by this point diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java -index 6c611afce3ca26e5b08f3341563a986d2a21142f..46230b09fc176665ba1d29ab3233c3b956a07710 100644 +index 8e363f2b72c7785667890878854d17e1fb593017..6a21c7b398e5491c95f34c9f5e674b4247cf2df5 100644 --- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java +++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java @@ -481,7 +481,7 @@ public class LevelChunk extends ChunkAccess { @@ -43,10 +43,10 @@ index 6c611afce3ca26e5b08f3341563a986d2a21142f..46230b09fc176665ba1d29ab3233c3b9 if (iblockdata.hasBlockEntity()) { diff --git a/src/main/java/net/minecraft/world/level/material/FlowingFluid.java b/src/main/java/net/minecraft/world/level/material/FlowingFluid.java -index 82cad323e4fba9aea082047d3eb6c4351681d9af..8a9b471b3cfff45a6c78d8fffa0efb3304152acb 100644 +index 9052a88b2ca32c61397f176f2a4fbce8415b64cb..4e86dd792ed0def86e245521397473a8bfd65c57 100644 --- a/src/main/java/net/minecraft/world/level/material/FlowingFluid.java +++ b/src/main/java/net/minecraft/world/level/material/FlowingFluid.java -@@ -520,6 +520,7 @@ public abstract class FlowingFluid extends Fluid { +@@ -518,6 +518,7 @@ public abstract class FlowingFluid extends Fluid { @Override public void tick(Level world, BlockPos pos, FluidState state) { @@ -91,61 +91,3 @@ index 2708251ebc1995e71fb0e5dca9e158a3005f8a8a..e28351b379677fec356b6efec2d882ee NeighborUpdater.executeUpdate(this.level, state, pos, sourceBlock, sourcePos, notify); } } -diff --git a/src/main/java/top/leavesmc/leaves/command/NoBlockUpdateCommand.java b/src/main/java/top/leavesmc/leaves/command/NoBlockUpdateCommand.java -new file mode 100644 -index 0000000000000000000000000000000000000000..819d5a5383dc6978f53645b727722fa924b7a505 ---- /dev/null -+++ b/src/main/java/top/leavesmc/leaves/command/NoBlockUpdateCommand.java -@@ -0,0 +1,52 @@ -+package top.leavesmc.leaves.command; -+ -+import net.kyori.adventure.text.Component; -+import net.kyori.adventure.text.JoinConfiguration; -+import net.kyori.adventure.text.format.NamedTextColor; -+import org.bukkit.Bukkit; -+import org.bukkit.command.Command; -+import org.bukkit.command.CommandSender; -+import org.bukkit.permissions.Permission; -+import org.bukkit.permissions.PermissionDefault; -+import org.bukkit.plugin.PluginManager; -+import org.jetbrains.annotations.NotNull; -+import top.leavesmc.leaves.LeavesConfig; -+ -+import java.util.List; -+ -+public class NoBlockUpdateCommand extends Command { -+ -+ private static boolean noBlockUpdate = false; -+ -+ public NoBlockUpdateCommand(@NotNull String name) { -+ super(name); -+ this.description = "No Block Update Command"; -+ this.usageMessage = "/blockupdate"; -+ this.setPermission("bukkit.command.blockupdate"); -+ final PluginManager pluginManager = Bukkit.getServer().getPluginManager(); -+ if (pluginManager.getPermission("bukkit.command.blockupdate") == null) { -+ pluginManager.addPermission(new Permission("bukkit.command.blockupdate", PermissionDefault.OP)); -+ } -+ } -+ -+ @Override -+ public @NotNull List tabComplete(@NotNull CommandSender sender, @NotNull String alias, @NotNull String[] args) throws IllegalArgumentException { -+ return List.of(); -+ } -+ -+ @Override -+ public boolean execute(@NotNull CommandSender sender, @NotNull String commandLabel, @NotNull String[] args) { -+ if (!testPermission(sender)) return true; -+ noBlockUpdate = !noBlockUpdate; -+ Bukkit.broadcast(Component.join(JoinConfiguration.noSeparators(), -+ Component.text("Block update status: ", NamedTextColor.GRAY), -+ Component.text(!noBlockUpdate, noBlockUpdate ? NamedTextColor.AQUA : NamedTextColor.GRAY) -+ ), "bukkit.command.blockupdate"); -+ -+ return true; -+ } -+ -+ public static boolean isNoBlockUpdate() { -+ return LeavesConfig.noBlockUpdateCommand && noBlockUpdate; -+ } -+} diff --git a/patches/server/0073-Raider-die-skip-self-raid-check.patch b/patches/server/0073-Raider-die-skip-self-raid-check.patch index 4e51d488..bffe5f03 100644 --- a/patches/server/0073-Raider-die-skip-self-raid-check.patch +++ b/patches/server/0073-Raider-die-skip-self-raid-check.patch @@ -5,15 +5,15 @@ Subject: [PATCH] Raider die skip self raid check diff --git a/src/main/java/net/minecraft/world/entity/raid/Raider.java b/src/main/java/net/minecraft/world/entity/raid/Raider.java -index 93bbf7556f9599e9dd90761085a57d78bd521867..e6af29b4934a3d2344ad97c4bbc23523e458ea6a 100644 +index 98e558338b5d9fb03869d2cc21b3e90eb45b95f6..09cfbc650d65faf67e3f11b30341355cf9c9e92c 100644 --- a/src/main/java/net/minecraft/world/entity/raid/Raider.java +++ b/src/main/java/net/minecraft/world/entity/raid/Raider.java -@@ -137,7 +137,7 @@ public abstract class Raider extends PatrollingMonster { - raid.removeFromRaid(this, false); - } +@@ -128,7 +128,7 @@ public abstract class Raider extends PatrollingMonster { + Raid raid = this.getCurrentRaid(); -- if (this.isPatrolLeader() && raid == null && ((ServerLevel) this.level()).getRaidAt(this.blockPosition()) == null) { -+ if (this.isPatrolLeader() && (top.leavesmc.leaves.LeavesConfig.skipSelfRaidCheck || raid == null) && ((ServerLevel) this.level()).getRaidAt(this.blockPosition()) == null) { // Leaves - skip self raid check - ItemStack itemstack = this.getItemBySlot(EquipmentSlot.HEAD); - Player entityhuman = null; + if (raid != null) { +- if (this.isPatrolLeader()) { ++ if (!top.leavesmc.leaves.LeavesConfig.skipSelfRaidCheck && this.isPatrolLeader()) { // Leaves - skip self raid check + raid.removeLeader(this.getWave()); + } diff --git a/patches/server/0074-Container-open-passthrough.patch b/patches/server/0074-Container-open-passthrough.patch index 6b31077a..4ed2bdb4 100644 --- a/patches/server/0074-Container-open-passthrough.patch +++ b/patches/server/0074-Container-open-passthrough.patch @@ -5,32 +5,32 @@ Subject: [PATCH] Container open passthrough diff --git a/src/main/java/net/minecraft/world/level/block/SignBlock.java b/src/main/java/net/minecraft/world/level/block/SignBlock.java -index 27a1e8ffc43efe4e086e7fd88ee4d80c23f98674..aafe4fd8b406f6b880ab37d39958e57550d5b6a1 100644 +index f7bae8060d993176799ff3ff4653d760a137faba..ec45b1aacc9244d08b7870f63a08a27a71499a79 100644 --- a/src/main/java/net/minecraft/world/level/block/SignBlock.java +++ b/src/main/java/net/minecraft/world/level/block/SignBlock.java -@@ -117,6 +117,25 @@ public abstract class SignBlock extends BaseEntityBlock implements SimpleWaterlo - return InteractionResult.SUCCESS; - } else if (flag2) { - return InteractionResult.SUCCESS; -+ // Leaves start - signContainerPassthrough -+ } else if (top.leavesmc.leaves.LeavesConfig.containerPassthrough) { -+ if (item == net.minecraft.world.item.Items.AIR && player.isShiftKeyDown()) { -+ if (!this.otherPlayerIsEditingSign(player, tileentitysign) && player.mayBuild() && this.hasEditableText(player, tileentitysign, flag1)) { -+ this.openTextEdit(player, tileentitysign, flag1); -+ return InteractionResult.SUCCESS; -+ } +@@ -141,6 +141,25 @@ public abstract class SignBlock extends BaseEntityBlock implements SimpleWaterlo + return InteractionResult.SUCCESS; + } else if (flag1) { + return InteractionResult.SUCCESS; ++ // Leaves start - signContainerPassthrough ++ } else if (top.leavesmc.leaves.LeavesConfig.containerPassthrough) { ++ if (item == net.minecraft.world.item.Items.AIR && player.isShiftKeyDown()) { ++ if (!this.otherPlayerIsEditingSign(player, tileentitysign) && player.mayBuild() && this.hasEditableText(player, tileentitysign, flag1)) { ++ this.openTextEdit(player, tileentitysign, flag1); ++ return InteractionResult.SUCCESS; + } ++ } + -+ BlockPos pos1 = pos.relative(hit.getDirection().getOpposite()); -+ if (this instanceof WallSignBlock || this instanceof WallHangingSignBlock) { -+ pos1 = pos.relative(state.getValue(HorizontalDirectionalBlock.FACING).getOpposite()); -+ } -+ if (world.getBlockEntity(pos1) instanceof net.minecraft.world.level.block.entity.RandomizableContainerBlockEntity) { -+ BlockState state1 = world.getBlockState(pos1); -+ return state1.use(world, player, hand, hit.withPosition(pos1)); -+ } -+ return InteractionResult.PASS; -+ // Leaves end - signContainerPassthrough - } else if (!this.otherPlayerIsEditingSign(player, tileentitysign) && player.mayBuild() && this.hasEditableText(player, tileentitysign, flag1)) { - this.openTextEdit(player, tileentitysign, flag1, io.papermc.paper.event.player.PlayerOpenSignEvent.Cause.INTERACT); // Paper - Add PlayerOpenSignEvent - return this.getInteractionResult(flag); ++ BlockPos pos1 = pos.relative(hit.getDirection().getOpposite()); ++ if (this instanceof WallSignBlock || this instanceof WallHangingSignBlock) { ++ pos1 = pos.relative(state.getValue(HorizontalDirectionalBlock.FACING).getOpposite()); ++ } ++ if (world.getBlockEntity(pos1) instanceof net.minecraft.world.level.block.entity.RandomizableContainerBlockEntity) { ++ BlockState state1 = world.getBlockState(pos1); ++ return state1.use(world, player, hand, hit.withPosition(pos1)); ++ } ++ return InteractionResult.PASS; ++ // Leaves end - signContainerPassthrough + } else if (!this.otherPlayerIsEditingSign(player, tileentitysign) && player.mayBuild() && this.hasEditableText(player, tileentitysign, flag)) { + this.openTextEdit(player, tileentitysign, flag, io.papermc.paper.event.player.PlayerOpenSignEvent.Cause.INTERACT); // Paper - Add PlayerOpenSignEvent + return InteractionResult.SUCCESS; diff --git a/patches/server/0075-Bladeren-Protocol.patch b/patches/server/0075-Bladeren-Protocol.patch deleted file mode 100644 index 3247c115..00000000 --- a/patches/server/0075-Bladeren-Protocol.patch +++ /dev/null @@ -1,157 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: violetc <58360096+s-yh-china@users.noreply.github.com> -Date: Fri, 7 Jul 2023 16:50:06 +0800 -Subject: [PATCH] Bladeren Protocol - - -diff --git a/src/main/java/top/leavesmc/leaves/protocol/bladeren/BladerenProtocol.java b/src/main/java/top/leavesmc/leaves/protocol/bladeren/BladerenProtocol.java -new file mode 100644 -index 0000000000000000000000000000000000000000..a91011c7a2c56646053bb9d158ff0c265c658c00 ---- /dev/null -+++ b/src/main/java/top/leavesmc/leaves/protocol/bladeren/BladerenProtocol.java -@@ -0,0 +1,145 @@ -+package top.leavesmc.leaves.protocol.bladeren; -+ -+import net.minecraft.nbt.CompoundTag; -+import net.minecraft.network.FriendlyByteBuf; -+import net.minecraft.network.protocol.common.custom.CustomPacketPayload; -+import net.minecraft.resources.ResourceLocation; -+import net.minecraft.server.level.ServerPlayer; -+import org.jetbrains.annotations.Contract; -+import org.jetbrains.annotations.NotNull; -+import top.leavesmc.leaves.LeavesConfig; -+import top.leavesmc.leaves.protocol.core.LeavesProtocol; -+import top.leavesmc.leaves.protocol.core.ProtocolHandler; -+import top.leavesmc.leaves.protocol.core.ProtocolUtils; -+ -+import java.util.HashMap; -+import java.util.Map; -+import java.util.function.BiConsumer; -+ -+@LeavesProtocol(namespace = "bladeren") -+public class BladerenProtocol { -+ -+ public static final String PROTOCOL_ID = "bladeren"; -+ public static final String PROTOCOL_VERSION = "1.0.0"; -+ -+ private static final ResourceLocation HELLO_ID = id("hello"); -+ private static final ResourceLocation FEATURE_MODIFY_ID = id("feature_modify"); -+ -+ private static final Map> registeredFeatures = new HashMap<>(); -+ -+ @Contract("_ -> new") -+ public static @NotNull ResourceLocation id(String path) { -+ return new ResourceLocation(PROTOCOL_ID, path); -+ } -+ -+ @ProtocolHandler.PayloadReceiver(payload = BladerenHelloPayload.class, payloadId = "hello") -+ private static void handleHello(@NotNull ServerPlayer player, @NotNull BladerenHelloPayload payload) { -+ if (LeavesConfig.bladerenLeavesProtocol) { -+ String clientVersion = payload.version; -+ CompoundTag tag = payload.nbt; -+ -+ if (tag != null) { -+ CompoundTag featureNbt = tag.getCompound("Features"); -+ for (String name : featureNbt.getAllKeys()) { -+ if (registeredFeatures.containsKey(name)) { -+ registeredFeatures.get(name).accept(player, featureNbt.getCompound(name)); -+ } -+ } -+ } -+ } -+ } -+ -+ @ProtocolHandler.PayloadReceiver(payload = BladerenFeatureModifyPayload.class, payloadId = "feature_modify") -+ private static void handleModify(@NotNull ServerPlayer player, @NotNull BladerenFeatureModifyPayload payload) { -+ if (LeavesConfig.bladerenLeavesProtocol) { -+ String name = payload.name; -+ CompoundTag tag = payload.nbt; -+ -+ if (registeredFeatures.containsKey(name)) { -+ registeredFeatures.get(name).accept(player, tag); -+ } -+ } -+ } -+ -+ @ProtocolHandler.PlayerJoin -+ public static void onPlayerJoin(@NotNull ServerPlayer player) { -+ if (LeavesConfig.bladerenLeavesProtocol) { -+ CompoundTag tag = new CompoundTag(); -+ LeavesFeatureSet.writeNBT(tag); -+ ProtocolUtils.sendPayloadPacket(player, new BladerenHelloPayload(PROTOCOL_VERSION, tag)); -+ } -+ } -+ -+ public static void registerFeature(String name, BiConsumer consumer) { -+ registeredFeatures.put(name, consumer); -+ } -+ -+ public static class LeavesFeatureSet { -+ -+ private static final Map features = new HashMap<>(); -+ -+ public static void writeNBT(@NotNull CompoundTag tag) { -+ CompoundTag featureNbt = new CompoundTag(); -+ features.values().forEach(feature -> feature.writeNBT(featureNbt)); -+ tag.put("Features", featureNbt); -+ } -+ -+ public static void register(LeavesFeature feature) { -+ features.put(feature.name, feature); -+ } -+ } -+ -+ public record LeavesFeature(String name, String value) { -+ -+ @NotNull -+ @Contract("_, _ -> new") -+ public static LeavesFeature of(String name, boolean value) { -+ return new LeavesFeature(name, Boolean.toString(value)); -+ } -+ -+ public void writeNBT(@NotNull CompoundTag rules) { -+ CompoundTag rule = new CompoundTag(); -+ rule.putString("Feature", name); -+ rule.putString("Value", value); -+ rules.put(name, rule); -+ } -+ } -+ -+ public record BladerenFeatureModifyPayload(String name, CompoundTag nbt) implements CustomPacketPayload { -+ -+ public BladerenFeatureModifyPayload(ResourceLocation location, FriendlyByteBuf buf) { -+ this(buf.readUtf(), buf.readNbt()); -+ } -+ -+ @Override -+ public void write(@NotNull FriendlyByteBuf buf) { -+ buf.writeUtf(name); -+ buf.writeNbt(nbt); -+ } -+ -+ @Override -+ @NotNull -+ public ResourceLocation id() { -+ return FEATURE_MODIFY_ID; -+ } -+ } -+ -+ public record BladerenHelloPayload(String version, CompoundTag nbt) implements CustomPacketPayload { -+ -+ public BladerenHelloPayload(ResourceLocation location, @NotNull FriendlyByteBuf buf) { -+ this(buf.readUtf(64), buf.readNbt()); -+ } -+ -+ @Override -+ public void write(@NotNull FriendlyByteBuf buf) { -+ buf.writeUtf(version); -+ buf.writeNbt(nbt); -+ } -+ -+ @Override -+ @NotNull -+ public ResourceLocation id() { -+ return HELLO_ID; -+ } -+ } -+} diff --git a/patches/server/0075-Placeholder-for-Bladeren-Protocol.patch b/patches/server/0075-Placeholder-for-Bladeren-Protocol.patch new file mode 100644 index 00000000..1526b462 --- /dev/null +++ b/patches/server/0075-Placeholder-for-Bladeren-Protocol.patch @@ -0,0 +1,15 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: MC_XiaoHei +Date: Wed, 8 May 2024 22:28:35 +0800 +Subject: [PATCH] Placeholder for Bladeren-Protocol + + +diff --git a/.gitignore b/.gitignore +index 1ad93453221244f880855b510e888e759275b640..3811c0d849a3eb028ed1a6b7a2d4747f7f570448 100644 +--- a/.gitignore ++++ b/.gitignore +@@ -46,4 +46,3 @@ dependency-reduced-pom.xml + # vs code + /.vscode + /.factorypath +- diff --git a/patches/server/0076-Bladeren-mspt-sync-protocol.patch b/patches/server/0076-Bladeren-mspt-sync-protocol.patch deleted file mode 100644 index 169d34e3..00000000 --- a/patches/server/0076-Bladeren-mspt-sync-protocol.patch +++ /dev/null @@ -1,85 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: violetc <58360096+s-yh-china@users.noreply.github.com> -Date: Mon, 3 Jul 2023 22:12:16 +0800 -Subject: [PATCH] Bladeren mspt sync protocol - - -diff --git a/src/main/java/top/leavesmc/leaves/protocol/bladeren/MsptSyncProtocol.java b/src/main/java/top/leavesmc/leaves/protocol/bladeren/MsptSyncProtocol.java -new file mode 100644 -index 0000000000000000000000000000000000000000..cf909f22f0860e6f56a86510dfcbd949a4c1fc32 ---- /dev/null -+++ b/src/main/java/top/leavesmc/leaves/protocol/bladeren/MsptSyncProtocol.java -@@ -0,0 +1,73 @@ -+package top.leavesmc.leaves.protocol.bladeren; -+ -+import net.minecraft.resources.ResourceLocation; -+import net.minecraft.server.MinecraftServer; -+import net.minecraft.server.level.ServerPlayer; -+import org.jetbrains.annotations.Contract; -+import org.jetbrains.annotations.NotNull; -+import top.leavesmc.leaves.LeavesConfig; -+import top.leavesmc.leaves.protocol.core.LeavesProtocol; -+import top.leavesmc.leaves.protocol.core.ProtocolHandler; -+import top.leavesmc.leaves.protocol.core.ProtocolUtils; -+ -+import java.util.ArrayList; -+import java.util.Arrays; -+import java.util.List; -+ -+@LeavesProtocol(namespace = "bladeren") -+public class MsptSyncProtocol { -+ -+ public static final String PROTOCOL_ID = "bladeren"; -+ -+ private static final ResourceLocation MSPT_SYNC = id("mspt_sync"); -+ -+ private static final List players = new ArrayList<>(); -+ -+ @Contract("_ -> new") -+ public static @NotNull ResourceLocation id(String path) { -+ return new ResourceLocation(PROTOCOL_ID, path); -+ } -+ -+ @ProtocolHandler.Init -+ public static void init() { -+ BladerenProtocol.registerFeature("mspt_sync", (player, compoundTag) -> { -+ if (compoundTag.getString("Value").equals("true")) { -+ onPlayerSubmit(player); -+ } else { -+ onPlayerLoggedOut(player); -+ } -+ }); -+ } -+ -+ @ProtocolHandler.PlayerLeave -+ public static void onPlayerLoggedOut(@NotNull ServerPlayer player) { -+ if (LeavesConfig.msptSyncProtocol) { -+ players.remove(player); -+ } -+ } -+ -+ @ProtocolHandler.Ticker -+ public static void tick() { -+ if (LeavesConfig.msptSyncProtocol) { -+ if (players.isEmpty()) { -+ return; -+ } -+ -+ MinecraftServer server = MinecraftServer.getServer(); -+ if (server.getTickCount() % LeavesConfig.msptSyncTickInterval == 0) { -+ double mspt = Arrays.stream(server.getTickTimesNanos()).average().getAsDouble() * 1.0E-6D; -+ double tps = 1000.0D / Math.max(mspt, 50); -+ players.forEach(player -> ProtocolUtils.sendPayloadPacket(player, MSPT_SYNC, buf -> { -+ buf.writeDouble(mspt); -+ buf.writeDouble(tps); -+ })); -+ } -+ } -+ } -+ -+ public static void onPlayerSubmit(@NotNull ServerPlayer player) { -+ if (LeavesConfig.msptSyncProtocol) { -+ players.add(player); -+ } -+ } -+} diff --git a/patches/server/0076-Placeholder-for-Bladeren-mspt-sync-protocol.patch b/patches/server/0076-Placeholder-for-Bladeren-mspt-sync-protocol.patch new file mode 100644 index 00000000..0a0343e3 --- /dev/null +++ b/patches/server/0076-Placeholder-for-Bladeren-mspt-sync-protocol.patch @@ -0,0 +1,15 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: MC_XiaoHei +Date: Wed, 8 May 2024 22:28:54 +0800 +Subject: [PATCH] Placeholder for Bladeren-mspt-sync-protocol + + +diff --git a/.gitignore b/.gitignore +index 3811c0d849a3eb028ed1a6b7a2d4747f7f570448..1ad93453221244f880855b510e888e759275b640 100644 +--- a/.gitignore ++++ b/.gitignore +@@ -46,3 +46,4 @@ dependency-reduced-pom.xml + # vs code + /.vscode + /.factorypath ++ diff --git a/patches/server/0077-SIMD-support.patch b/patches/server/0077-SIMD-support.patch index 631bb6cf..bf0bfd70 100644 --- a/patches/server/0077-SIMD-support.patch +++ b/patches/server/0077-SIMD-support.patch @@ -5,10 +5,10 @@ Subject: [PATCH] SIMD support diff --git a/build.gradle.kts b/build.gradle.kts -index a2441f300345c3936bce69d6d3f21fdd6626136d..77d6f5eeeb9918a70e0003e97671778303c33ce9 100644 +index ae81fc7e76a1d9cd0308ae321872f2635c2dab39..48a066ecdeba8acf6c84d11add54a7f4e038f073 100644 --- a/build.gradle.kts +++ b/build.gradle.kts -@@ -70,6 +70,7 @@ tasks.withType { +@@ -77,6 +77,7 @@ tasks.withType { compilerArgs.add("-Xlint:-module") compilerArgs.add("-Xlint:-removal") compilerArgs.add("-Xlint:-dep-ann") @@ -16,7 +16,7 @@ index a2441f300345c3936bce69d6d3f21fdd6626136d..77d6f5eeeb9918a70e0003e976717783 } // Leaves end - hide irrelevant compilation warnings -@@ -189,6 +190,8 @@ fun TaskContainer.registerRunTask( +@@ -180,6 +181,8 @@ fun TaskContainer.registerRunTask( minHeapSize = "${memoryGb}G" maxHeapSize = "${memoryGb}G" diff --git a/patches/server/0078-Dont-respond-ping-before-start-fully.patch b/patches/server/0078-Dont-respond-ping-before-start-fully.patch index 56b907ba..463578b5 100644 --- a/patches/server/0078-Dont-respond-ping-before-start-fully.patch +++ b/patches/server/0078-Dont-respond-ping-before-start-fully.patch @@ -6,7 +6,7 @@ Subject: [PATCH] Dont respond ping before start fully This patch is Powered by Gale(https://github.com/GaleMC/Gale) diff --git a/src/main/java/net/minecraft/server/network/ServerStatusPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerStatusPacketListenerImpl.java -index e5006e7672ba79ed4bcf2c4173c5a9ed4c68395b..06093105d58db26ae4b7dac80bce659995eeaf0f 100644 +index 6f1c9fa89e718cbc01a8d72de34154f49c5f46db..b47de383a1a37f6855f5ee4124df8c35be630404 100644 --- a/src/main/java/net/minecraft/server/network/ServerStatusPacketListenerImpl.java +++ b/src/main/java/net/minecraft/server/network/ServerStatusPacketListenerImpl.java @@ -153,6 +153,12 @@ public class ServerStatusPacketListenerImpl implements ServerStatusPacketListene diff --git a/patches/server/0079-Faster-chunk-serialization.patch b/patches/server/0079-Faster-chunk-serialization.patch index 90179dd7..832d72ed 100644 --- a/patches/server/0079-Faster-chunk-serialization.patch +++ b/patches/server/0079-Faster-chunk-serialization.patch @@ -112,7 +112,7 @@ index acae3eb30e0689048937f479dc3070f0688abdad..029b62acf1d9f8479ab64a55c12f00ba int onResize(int newBits, T object); } diff --git a/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java b/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java -index adfbdca12fbdee2602feb0158674166d5256e49e..7f823b131c3a50a99077665ed688b53862b9fb52 100644 +index 81368bf186365878db2e1ed305bb7bf36c26f61f..eae31d1a646ad75c1548e08cc48189cc48e453bc 100644 --- a/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java +++ b/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java @@ -23,8 +23,25 @@ import net.minecraft.util.Mth; diff --git a/patches/server/0080-Optimize-world-generation-chunk-and-block-access.patch b/patches/server/0080-Optimize-world-generation-chunk-and-block-access.patch index d6006c3e..d27e8828 100644 --- a/patches/server/0080-Optimize-world-generation-chunk-and-block-access.patch +++ b/patches/server/0080-Optimize-world-generation-chunk-and-block-access.patch @@ -6,10 +6,10 @@ Subject: [PATCH] Optimize world generation chunk and block access This patch is Powered by Gale(https://github.com/GaleMC/Gale) diff --git a/src/main/java/net/minecraft/server/level/WorldGenRegion.java b/src/main/java/net/minecraft/server/level/WorldGenRegion.java -index 5ece375eaf6bcc61864997a389bb5e24625e4505..68d806b4e397f9f90bd66138cbdd23f3108b87e7 100644 +index 1351423a12c19a01f602a202832372a399e6a867..12f4678622ad74ddbe8fe003a71005dd356881c4 100644 --- a/src/main/java/net/minecraft/server/level/WorldGenRegion.java +++ b/src/main/java/net/minecraft/server/level/WorldGenRegion.java -@@ -83,6 +83,10 @@ public class WorldGenRegion implements WorldGenLevel { +@@ -86,6 +86,10 @@ public class WorldGenRegion implements WorldGenLevel { private Supplier currentlyGenerating; private final AtomicLong subTickCount = new AtomicLong(); private static final ResourceLocation WORLDGEN_REGION_RANDOM = new ResourceLocation("worldgen_region_random"); @@ -20,9 +20,9 @@ index 5ece375eaf6bcc61864997a389bb5e24625e4505..68d806b4e397f9f90bd66138cbdd23f3 public WorldGenRegion(ServerLevel world, List chunks, ChunkStatus status, int placementRadius) { this.generatingStatus = status; -@@ -105,6 +109,11 @@ public class WorldGenRegion implements WorldGenLevel { +@@ -107,6 +111,11 @@ public class WorldGenRegion implements WorldGenLevel { + this.firstPos = ((ChunkAccess) chunks.get(0)).getPos(); this.lastPos = ((ChunkAccess) chunks.get(chunks.size() - 1)).getPos(); - this.structureManager = world.structureManager().forWorldGenRegion(this); } + // Leaves start - optimize world generation chunk and block access + this.minChunkX = this.firstPos.x; @@ -32,7 +32,7 @@ index 5ece375eaf6bcc61864997a389bb5e24625e4505..68d806b4e397f9f90bd66138cbdd23f3 } // Paper start - starlight -@@ -143,8 +152,29 @@ public class WorldGenRegion implements WorldGenLevel { +@@ -145,8 +154,29 @@ public class WorldGenRegion implements WorldGenLevel { @Override public ChunkAccess getChunk(int chunkX, int chunkZ) { @@ -63,7 +63,7 @@ index 5ece375eaf6bcc61864997a389bb5e24625e4505..68d806b4e397f9f90bd66138cbdd23f3 @Nullable @Override -@@ -203,7 +233,21 @@ public class WorldGenRegion implements WorldGenLevel { +@@ -211,7 +241,21 @@ public class WorldGenRegion implements WorldGenLevel { @Override public BlockState getBlockState(BlockPos pos) { @@ -86,113 +86,3 @@ index 5ece375eaf6bcc61864997a389bb5e24625e4505..68d806b4e397f9f90bd66138cbdd23f3 } @Override -diff --git a/src/main/java/top/leavesmc/leaves/lithium/common/util/Pos.java b/src/main/java/top/leavesmc/leaves/lithium/common/util/Pos.java -new file mode 100644 -index 0000000000000000000000000000000000000000..f1650846d080ab743aa324a3fe94447e2b3d7ece ---- /dev/null -+++ b/src/main/java/top/leavesmc/leaves/lithium/common/util/Pos.java -@@ -0,0 +1,104 @@ -+ -+package top.leavesmc.leaves.lithium.common.util; -+ -+import net.minecraft.core.SectionPos; -+import net.minecraft.world.level.LevelHeightAccessor; -+ -+public class Pos { -+ -+ public static class BlockCoord { -+ public static int getYSize(LevelHeightAccessor view) { -+ return view.getHeight(); -+ } -+ -+ public static int getMinY(LevelHeightAccessor view) { -+ return view.getMinBuildHeight(); -+ } -+ -+ public static int getMaxYInclusive(LevelHeightAccessor view) { -+ return view.getMaxBuildHeight() - 1; -+ } -+ -+ public static int getMaxYExclusive(LevelHeightAccessor view) { -+ return view.getMaxBuildHeight(); -+ } -+ -+ public static int getMaxInSectionCoord(int sectionCoord) { -+ return 15 + getMinInSectionCoord(sectionCoord); -+ } -+ -+ public static int getMaxYInSectionIndex(LevelHeightAccessor view, int sectionIndex) { -+ return getMaxInSectionCoord(SectionYCoord.fromSectionIndex(view, sectionIndex)); -+ } -+ -+ public static int getMinInSectionCoord(int sectionCoord) { -+ return SectionPos.sectionToBlockCoord(sectionCoord); -+ } -+ -+ public static int getMinYInSectionIndex(LevelHeightAccessor view, int sectionIndex) { -+ return getMinInSectionCoord(SectionYCoord.fromSectionIndex(view, sectionIndex)); -+ } -+ } -+ -+ public static class ChunkCoord { -+ public static int fromBlockCoord(int blockCoord) { -+ return SectionPos.blockToSectionCoord(blockCoord); -+ } -+ -+ public static int fromBlockSize(int i) { -+ return i >> 4; //same method as fromBlockCoord, just be clear about coord/size semantic difference -+ } -+ } -+ -+ public static class SectionYCoord { -+ public static int getNumYSections(LevelHeightAccessor view) { -+ return view.getSectionsCount(); -+ } -+ -+ public static int getMinYSection(LevelHeightAccessor view) { -+ return view.getMinSection(); -+ } -+ -+ public static int getMaxYSectionInclusive(LevelHeightAccessor view) { -+ return view.getMaxSection() - 1; -+ } -+ -+ public static int getMaxYSectionExclusive(LevelHeightAccessor view) { -+ return view.getMaxSection(); -+ } -+ -+ public static int fromSectionIndex(LevelHeightAccessor view, int sectionCoord) { -+ return sectionCoord + SectionYCoord.getMinYSection(view); -+ } -+ -+ public static int fromBlockCoord(int blockCoord) { -+ return SectionPos.blockToSectionCoord(blockCoord); -+ } -+ } -+ -+ public static class SectionYIndex { -+ public static int getNumYSections(LevelHeightAccessor view) { -+ return view.getSectionsCount(); -+ } -+ -+ public static int getMinYSectionIndex(LevelHeightAccessor view) { -+ return 0; -+ } -+ -+ public static int getMaxYSectionIndexInclusive(LevelHeightAccessor view) { -+ return view.getSectionsCount() - 1; -+ } -+ -+ public static int getMaxYSectionIndexExclusive(LevelHeightAccessor view) { -+ return view.getSectionsCount(); -+ } -+ -+ public static int fromSectionCoord(LevelHeightAccessor view, int sectionCoord) { -+ return sectionCoord - SectionYCoord.getMinYSection(view); -+ } -+ -+ public static int fromBlockCoord(LevelHeightAccessor view, int blockCoord) { -+ return fromSectionCoord(view, SectionPos.blockToSectionCoord(blockCoord)); -+ } -+ } -+} diff --git a/patches/server/0081-Cache-world-generator-sea-level.patch b/patches/server/0081-Cache-world-generator-sea-level.patch index e97e5953..942b6155 100644 --- a/patches/server/0081-Cache-world-generator-sea-level.patch +++ b/patches/server/0081-Cache-world-generator-sea-level.patch @@ -6,7 +6,7 @@ Subject: [PATCH] Cache world generator sea level This patch is Powered by Gale(https://github.com/GaleMC/Gale) diff --git a/src/main/java/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java b/src/main/java/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java -index 98c7f695093acbcf9382a5f07a7a89e373709763..5cb228f3362b87418561ed3a77f4a241ddb8e9b5 100644 +index 8ce870a5341a61fbbaf42021ef7f7f615a6a3e09..1ecaa7d684beca59aa7e6f39aab9385da256c0ad 100644 --- a/src/main/java/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java +++ b/src/main/java/net/minecraft/world/level/levelgen/NoiseBasedChunkGenerator.java @@ -62,12 +62,17 @@ public final class NoiseBasedChunkGenerator extends ChunkGenerator { diff --git a/patches/server/0082-Skip-secondary-POI-sensor-if-absent.patch b/patches/server/0082-Skip-secondary-POI-sensor-if-absent.patch index bda3c447..668ceaa0 100644 --- a/patches/server/0082-Skip-secondary-POI-sensor-if-absent.patch +++ b/patches/server/0082-Skip-secondary-POI-sensor-if-absent.patch @@ -6,7 +6,7 @@ Subject: [PATCH] Skip secondary POI sensor if absent This patch is Powered by Gale(https://github.com/GaleMC/Gale) diff --git a/src/main/java/net/minecraft/world/entity/ai/sensing/SecondaryPoiSensor.java b/src/main/java/net/minecraft/world/entity/ai/sensing/SecondaryPoiSensor.java -index cb1d91f9fe98f21c2afbe3894dfd9bca3bdd3ba6..ea89cf4a0746ecf1fdb1eae27560299b813d4374 100644 +index a0e0692d17760f440fe81d52887284c787e562db..db29a92e58b544702daac1f6804617c2f3b0841c 100644 --- a/src/main/java/net/minecraft/world/entity/ai/sensing/SecondaryPoiSensor.java +++ b/src/main/java/net/minecraft/world/entity/ai/sensing/SecondaryPoiSensor.java @@ -22,6 +22,15 @@ public class SecondaryPoiSensor extends Sensor { diff --git a/patches/server/0083-Cache-CubeVoxelShape-shape-array.patch b/patches/server/0083-Cache-CubeVoxelShape-shape-array.patch index 0d13abfd..2c22c905 100644 --- a/patches/server/0083-Cache-CubeVoxelShape-shape-array.patch +++ b/patches/server/0083-Cache-CubeVoxelShape-shape-array.patch @@ -6,7 +6,7 @@ Subject: [PATCH] Cache CubeVoxelShape shape array This patch is Powered by Gale(https://github.com/GaleMC/Gale) diff --git a/src/main/java/net/minecraft/world/phys/shapes/CubeVoxelShape.java b/src/main/java/net/minecraft/world/phys/shapes/CubeVoxelShape.java -index 110405e6e70d980d3e09f04d79562b32a7413071..8e44c1ae9f898f943d8c74fe34447546677be7d4 100644 +index b9af1d14c7815c99273bce8165cf384d669c1a75..e8aa4995e19d39fabb5831adaa20c4679c7c12e1 100644 --- a/src/main/java/net/minecraft/world/phys/shapes/CubeVoxelShape.java +++ b/src/main/java/net/minecraft/world/phys/shapes/CubeVoxelShape.java @@ -5,6 +5,9 @@ import net.minecraft.core.Direction; diff --git a/patches/server/0085-Cache-BlockStatePairKey-hash.patch b/patches/server/0085-Cache-BlockStatePairKey-hash.patch index e882717f..839c7f63 100644 --- a/patches/server/0085-Cache-BlockStatePairKey-hash.patch +++ b/patches/server/0085-Cache-BlockStatePairKey-hash.patch @@ -6,10 +6,10 @@ Subject: [PATCH] Cache BlockStatePairKey hash This patch is Powered by Gale(https://github.com/GaleMC/Gale) diff --git a/src/main/java/net/minecraft/world/level/block/Block.java b/src/main/java/net/minecraft/world/level/block/Block.java -index feab3cc5724a1fc44374a30c75dca9e3a3721adc..10c0fdfec1fcec3f9f7897685b2c7e00dc291b2d 100644 +index e27f2317e4e2f13b6ef12be727046497a750fd3a..3164627faa8bca0223c4614bec181b1d36d17840 100644 --- a/src/main/java/net/minecraft/world/level/block/Block.java +++ b/src/main/java/net/minecraft/world/level/block/Block.java -@@ -641,11 +641,18 @@ public class Block extends BlockBehaviour implements ItemLike { +@@ -602,11 +602,18 @@ public class Block extends BlockBehaviour implements ItemLike { private final BlockState first; private final BlockState second; private final Direction direction; @@ -28,7 +28,7 @@ index feab3cc5724a1fc44374a30c75dca9e3a3721adc..10c0fdfec1fcec3f9f7897685b2c7e00 } public boolean equals(Object object) { -@@ -661,11 +668,17 @@ public class Block extends BlockBehaviour implements ItemLike { +@@ -622,11 +629,17 @@ public class Block extends BlockBehaviour implements ItemLike { } public int hashCode() { diff --git a/patches/server/0087-Disable-packet-limit.patch b/patches/server/0087-Disable-packet-limit.patch index c89849ea..6b4f4c3f 100644 --- a/patches/server/0087-Disable-packet-limit.patch +++ b/patches/server/0087-Disable-packet-limit.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Disable packet limit diff --git a/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java b/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java -index dd402f077922ea3946a18cdf04285c67fd093538..41372edb33ac41f92bd1b0351cd2cf2e89d888df 100644 +index 0cb47b5fee85230989293d430c02e53391e0ef67..674049efc5fba8ddd7539c02aa5869844fef68c1 100644 --- a/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java +++ b/src/main/java/io/papermc/paper/configuration/GlobalConfiguration.java -@@ -267,7 +267,7 @@ public class GlobalConfiguration extends ConfigurationPart { +@@ -265,7 +265,7 @@ public class GlobalConfiguration extends ConfigurationPart { } public boolean isEnabled() { diff --git a/patches/server/0088-Reduce-array-allocations.patch b/patches/server/0088-Reduce-array-allocations.patch index 033b70e6..60b444ed 100644 --- a/patches/server/0088-Reduce-array-allocations.patch +++ b/patches/server/0088-Reduce-array-allocations.patch @@ -142,7 +142,7 @@ index 06648f9751fd8a322d0809ffebf6a544596ee1a4..40e957a4364c8017072dcd81fcb7cf2c @Override diff --git a/src/main/java/net/minecraft/nbt/CompoundTag.java b/src/main/java/net/minecraft/nbt/CompoundTag.java -index 23916b011ed0645ab284fb080c9555921290d875..83ace0d17691ec743a4b92de34d4de5a9ac6ff5b 100644 +index 4e005b7b062e3231f564d284887ea1c2783a4e7d..ff931c9b77b6739ada9f31a016872f4c51766ab3 100644 --- a/src/main/java/net/minecraft/nbt/CompoundTag.java +++ b/src/main/java/net/minecraft/nbt/CompoundTag.java @@ -18,6 +18,7 @@ import javax.annotation.Nullable; @@ -202,10 +202,10 @@ index ff13d67151c50ea11a45117e524c7524e2b1a202..c25d23c9c2ed887ce9ae7f07cd8aa0d1 @Override diff --git a/src/main/java/net/minecraft/network/Connection.java b/src/main/java/net/minecraft/network/Connection.java -index 35d03c7128e0cc9b3e0a9ecf109c17dab927c4a4..e6036459ee5dcd70ca9b945eca12114afb895e3f 100644 +index 02833deaa2bb7e5abc655bc1bdbe15c4b3ac7119..6814e99e12ea9d967a83341bd715f5da076a734e 100644 --- a/src/main/java/net/minecraft/network/Connection.java +++ b/src/main/java/net/minecraft/network/Connection.java -@@ -57,6 +57,7 @@ import org.apache.commons.lang3.Validate; +@@ -66,6 +66,7 @@ import org.apache.commons.lang3.Validate; import org.slf4j.Logger; import org.slf4j.Marker; import org.slf4j.MarkerFactory; @@ -214,10 +214,10 @@ index 35d03c7128e0cc9b3e0a9ecf109c17dab927c4a4..e6036459ee5dcd70ca9b945eca12114a public class Connection extends SimpleChannelInboundHandler> { diff --git a/src/main/java/net/minecraft/server/level/ServerEntity.java b/src/main/java/net/minecraft/server/level/ServerEntity.java -index b2183c298b0c68daee41238735621ff002338a68..3957ab1ebd7e84cfe0bef81a8cce566dca9972dc 100644 +index b9a171d200539c48659bc6ae7eb8abf5cf654ece..907c98b965edf8b57575bd62133f62690229de7a 100644 --- a/src/main/java/net/minecraft/server/level/ServerEntity.java +++ b/src/main/java/net/minecraft/server/level/ServerEntity.java -@@ -354,7 +354,7 @@ public class ServerEntity { +@@ -361,7 +361,7 @@ public class ServerEntity { if (this.entity instanceof LivingEntity) { List> list = Lists.newArrayList(); @@ -227,18 +227,18 @@ index b2183c298b0c68daee41238735621ff002338a68..3957ab1ebd7e84cfe0bef81a8cce566d for (int j = 0; j < i; ++j) { diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index 9e95045f10904744f7cbf6e4203f8cbe762a9afc..d27633948861fe72480126cd16b4daa40c5cced0 100644 +index 9fdbf663b3bfd51efb5a0efbc0f64eae29d8c7a6..c7da7d605c32ab523eb7a2aa08af5761c0e3189d 100644 --- a/src/main/java/net/minecraft/server/level/ServerLevel.java +++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -177,6 +177,7 @@ import org.bukkit.event.weather.LightningStrikeEvent; - import org.bukkit.event.world.GenericGameEvent; +@@ -180,6 +180,7 @@ import org.bukkit.event.server.MapInitializeEvent; + import org.bukkit.event.weather.LightningStrikeEvent; import org.bukkit.event.world.TimeSkipEvent; // CraftBukkit end +import top.leavesmc.leaves.util.ArrayConstants; public class ServerLevel extends Level implements WorldGenLevel { -@@ -1069,7 +1070,7 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -1075,7 +1076,7 @@ public class ServerLevel extends Level implements WorldGenLevel { BlockPos blockposition2 = blockposition.set(j + randomX, randomY, k + randomZ); BlockState iblockdata = com.destroystokyo.paper.util.maplist.IBlockDataList.getBlockDataFromRaw(raw); @@ -247,7 +247,7 @@ index 9e95045f10904744f7cbf6e4203f8cbe762a9afc..d27633948861fe72480126cd16b4daa4 } // We drop the fluid tick since LAVA is ALREADY TICKED by the above method (See LiquidBlock). // TODO CHECK ON UPDATE (ping the Canadian) -@@ -1375,7 +1376,7 @@ public class ServerLevel extends Level implements WorldGenLevel { +@@ -1381,7 +1382,7 @@ public class ServerLevel extends Level implements WorldGenLevel { public static List getCurrentlyTickingEntities() { Entity ticking = currentlyTickingEntity.get(); @@ -257,10 +257,10 @@ index 9e95045f10904744f7cbf6e4203f8cbe762a9afc..d27633948861fe72480126cd16b4daa4 return ret; } diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 3867eded99e72de706655d875d2e47ae5b135b48..2730718b162c20110145798dbb0eaad0b139e99b 100644 +index 9c9b702948b5a361a4cae0a177b1b298e202d64e..5d209d1e0d430c4d959d8d8c27153af8532c07da 100644 --- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -242,6 +242,7 @@ import org.bukkit.inventory.EquipmentSlot; +@@ -249,6 +249,7 @@ import org.bukkit.inventory.EquipmentSlot; import org.bukkit.inventory.InventoryView; import org.bukkit.inventory.SmithingInventory; // CraftBukkit end @@ -268,7 +268,7 @@ index 3867eded99e72de706655d875d2e47ae5b135b48..2730718b162c20110145798dbb0eaad0 public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl implements ServerGamePacketListener, ServerPlayerConnection, TickablePacketListener { -@@ -789,7 +790,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl +@@ -800,7 +801,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl // Paper start final int index; if (packet.getCommand().length() > 64 && ((index = packet.getCommand().indexOf(' ')) == -1 || index >= 64)) { @@ -278,29 +278,33 @@ index 3867eded99e72de706655d875d2e47ae5b135b48..2730718b162c20110145798dbb0eaad0 } // Paper end diff --git a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java -index c5fa9f4d28f9a7f64a50a902ee5e631bfc00119c..4f93d69b51f91e25f1ee00300b553311ce9c2f7d 100644 +index b656741eb68adeb04bf995f1045902cb6bd5f2e7..1380133f790410878d64496c845ea7a1b83570cc 100644 --- a/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java +++ b/src/main/java/net/minecraft/server/network/ServerLoginPacketListenerImpl.java -@@ -44,6 +44,7 @@ import org.bukkit.craftbukkit.util.Waitable; +@@ -49,6 +49,7 @@ import org.bukkit.craftbukkit.entity.CraftPlayer; + import org.bukkit.craftbukkit.util.Waitable; import org.bukkit.event.player.AsyncPlayerPreLoginEvent; import org.bukkit.event.player.PlayerPreLoginEvent; - // CraftBukkit end +import top.leavesmc.leaves.util.ArrayConstants; - public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener, TickablePacketListener { + public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener, TickablePacketListener, CraftPlayer.TransferCookieConnection { -@@ -138,8 +139,8 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener, +@@ -165,12 +166,12 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener, @Override public void handleHello(ServerboundHelloPacket packet) { - Validate.validState(this.state == ServerLoginPacketListenerImpl.State.HELLO, "Unexpected hello packet", new Object[0]); -- if (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.isProxyOnlineMode() && io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.performUsernameValidation && !this.iKnowThisMayNotBeTheBestIdeaButPleaseDisableUsernameValidation) Validate.validState(Player.isValidUsername(packet.name()), "Invalid characters in username", new Object[0]); // Paper - config username validation + Validate.validState(this.state == ServerLoginPacketListenerImpl.State.HELLO, "Unexpected hello packet", ArrayConstants.emptyObjectArray); // Leaves - reduce array allocations -+ if (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.isProxyOnlineMode() && io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.performUsernameValidation && !this.iKnowThisMayNotBeTheBestIdeaButPleaseDisableUsernameValidation) Validate.validState(Player.isValidUsername(packet.name()), "Invalid characters in username", ArrayConstants.emptyObjectArray); // Paper - config username validation // Leaves - reduce array allocations + // Paper start - Validate usernames + if (io.papermc.paper.configuration.GlobalConfiguration.get().proxies.isProxyOnlineMode() + && io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.performUsernameValidation + && !this.iKnowThisMayNotBeTheBestIdeaButPleaseDisableUsernameValidation) { +- Validate.validState(StringUtil.isReasonablePlayerName(packet.name()), "Invalid characters in username", new Object[0]); ++ Validate.validState(StringUtil.isReasonablePlayerName(packet.name()), "Invalid characters in username", ArrayConstants.emptyObjectArray); // Leaves - reduce array allocations + } + // Paper end - Validate usernames this.requestedUsername = packet.name(); - GameProfile gameprofile = this.server.getSingleplayerProfile(); - -@@ -223,7 +224,7 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener, +@@ -268,7 +269,7 @@ public class ServerLoginPacketListenerImpl implements ServerLoginPacketListener, @Override public void handleKey(ServerboundKeyPacket packet) { @@ -310,19 +314,19 @@ index c5fa9f4d28f9a7f64a50a902ee5e631bfc00119c..4f93d69b51f91e25f1ee00300b553311 final String s; diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index 6e5a7edfbc8781def735d0b2250888288560fbc4..139129f0110dc656bc729ce795f012ec087641cf 100644 +index 7d99a70c1eb3aecb2da01557d7c132c15d417fee..4e66d7c2d318594338f0162cd8f6e4a6a5138130 100644 --- a/src/main/java/net/minecraft/server/players/PlayerList.java +++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -123,6 +123,7 @@ import org.bukkit.event.player.PlayerSpawnChangeEvent; - - import top.leavesmc.leaves.bot.ServerBot; +@@ -27,6 +27,7 @@ import javax.annotation.Nullable; + import net.minecraft.ChatFormatting; + import net.minecraft.FileUtil; import top.leavesmc.leaves.util.ReturnPortalManager; // Leaves - return portal fix +import top.leavesmc.leaves.util.ArrayConstants; - - public abstract class PlayerList { - + import net.minecraft.commands.CommandSourceStack; + import net.minecraft.core.BlockPos; + import net.minecraft.core.LayeredRegistryAccess; diff --git a/src/main/java/net/minecraft/server/players/StoredUserList.java b/src/main/java/net/minecraft/server/players/StoredUserList.java -index 7e133752ccb1ea7c0b4fa781feb1a88e2cfdcf6d..b2a8a6f3a6a2934f30d9212bd935651056551be8 100644 +index c038da20b76c0b7b1c18471b20be01e849d29f3a..bb1f857eda62e0220c4ed0fefe5b6be217676732 100644 --- a/src/main/java/net/minecraft/server/players/StoredUserList.java +++ b/src/main/java/net/minecraft/server/players/StoredUserList.java @@ -24,6 +24,7 @@ import javax.annotation.Nullable; @@ -359,22 +363,22 @@ index 54c7d9bed0fead691d06691086f04cc2521dbe07..cebccf16bb0ea0a5085e3ab312ae570b public ZeroBitStorage(int size) { diff --git a/src/main/java/net/minecraft/world/entity/EquipmentSlot.java b/src/main/java/net/minecraft/world/entity/EquipmentSlot.java -index 8b4dd6ff53d2d61c71f04c37389ac645766305c4..0ff2e701439128abd8ac1bdb3ad9ccf7ff81528d 100644 +index eb3c12e03c0d5c9cec84d97e2c51c50ce59c23a4..0593d76c67f4475d624c5289b65c5d4a3c91e4cd 100644 --- a/src/main/java/net/minecraft/world/entity/EquipmentSlot.java +++ b/src/main/java/net/minecraft/world/entity/EquipmentSlot.java -@@ -15,6 +15,7 @@ public enum EquipmentSlot implements StringRepresentable { +@@ -16,6 +16,7 @@ public enum EquipmentSlot implements StringRepresentable { private final int index; private final int filterFlag; private final String name; + public static final EquipmentSlot[] VALUES = EquipmentSlot.values(); // Leaves - reduce array allocations - private EquipmentSlot(EquipmentSlot.Type type, int entityId, int armorStandId, String name) { + private EquipmentSlot(final EquipmentSlot.Type type, final int entityId, final int armorStandId, final String name) { this.type = type; diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index ffa9ada2e803bc77f8cae4be81ac4b5c2eee0b7f..0a038e46f68011391f5dee5f7609d3192ea0f8da 100644 +index c27d3caaf53d8d745fa5d054f57c9de50e60e2de..2d1639b5bbac10a37892b137aa24722d8fbdf254 100644 --- a/src/main/java/net/minecraft/world/entity/LivingEntity.java +++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -3166,7 +3166,7 @@ public abstract class LivingEntity extends Entity implements Attackable { +@@ -3238,7 +3238,7 @@ public abstract class LivingEntity extends Entity implements Attackable { @Nullable private Map collectEquipmentChanges() { Map map = null; @@ -384,10 +388,10 @@ index ffa9ada2e803bc77f8cae4be81ac4b5c2eee0b7f..0a038e46f68011391f5dee5f7609d319 for (int j = 0; j < i; ++j) { diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java -index 06c80f363fec3e9c824b9bbbcb7608fff7659c41..a4a0f99784ba5a0af851ff43eaf4ca10b6e96b1d 100644 +index 609abfd7d554812fc302347a8e06bf9dd751e72f..588e6cb991d8a58d067f391e41f10834454113f8 100644 --- a/src/main/java/net/minecraft/world/entity/Mob.java +++ b/src/main/java/net/minecraft/world/entity/Mob.java -@@ -1089,7 +1089,7 @@ public abstract class Mob extends LivingEntity implements Targeting { +@@ -1201,7 +1201,7 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Targeti @Override protected void dropCustomDeathLoot(DamageSource source, int lootingMultiplier, boolean allowDrops) { super.dropCustomDeathLoot(source, lootingMultiplier, allowDrops); @@ -396,7 +400,7 @@ index 06c80f363fec3e9c824b9bbbcb7608fff7659c41..a4a0f99784ba5a0af851ff43eaf4ca10 int j = aenumitemslot.length; for (int k = 0; k < j; ++k) { -@@ -1152,7 +1152,7 @@ public abstract class Mob extends LivingEntity implements Targeting { +@@ -1284,7 +1284,7 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Targeti } boolean flag = true; @@ -405,7 +409,7 @@ index 06c80f363fec3e9c824b9bbbcb7608fff7659c41..a4a0f99784ba5a0af851ff43eaf4ca10 int j = aenumitemslot.length; for (int k = 0; k < j; ++k) { -@@ -1239,7 +1239,7 @@ public abstract class Mob extends LivingEntity implements Targeting { +@@ -1371,7 +1371,7 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Targeti float f = localDifficulty.getSpecialMultiplier(); this.enchantSpawnedWeapon(random, f); @@ -414,7 +418,7 @@ index 06c80f363fec3e9c824b9bbbcb7608fff7659c41..a4a0f99784ba5a0af851ff43eaf4ca10 int i = aenumitemslot.length; for (int j = 0; j < i; ++j) { -@@ -1459,7 +1459,7 @@ public abstract class Mob extends LivingEntity implements Targeting { +@@ -1591,7 +1591,7 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Targeti t0.setInvulnerable(this.isInvulnerable()); if (flag) { t0.setCanPickUpLoot(this.canPickUpLoot()); @@ -424,10 +428,10 @@ index 06c80f363fec3e9c824b9bbbcb7608fff7659c41..a4a0f99784ba5a0af851ff43eaf4ca10 for (int j = 0; j < i; ++j) { diff --git a/src/main/java/net/minecraft/world/entity/monster/ZombieVillager.java b/src/main/java/net/minecraft/world/entity/monster/ZombieVillager.java -index 7de9d012e7416eaa0189b513a0972c846e93c4b6..505cae0013a501cbff094a83c491af963087d58f 100644 +index f38acc96f71298e40ce9433e7759fd223ca55e48..03652ec451109d2026fc313f143d78d4b855dd51 100644 --- a/src/main/java/net/minecraft/world/entity/monster/ZombieVillager.java +++ b/src/main/java/net/minecraft/world/entity/monster/ZombieVillager.java -@@ -237,7 +237,7 @@ public class ZombieVillager extends Zombie implements VillagerDataHolder { +@@ -238,7 +238,7 @@ public class ZombieVillager extends Zombie implements VillagerDataHolder { return; } // CraftBukkit end @@ -437,23 +441,23 @@ index 7de9d012e7416eaa0189b513a0972c846e93c4b6..505cae0013a501cbff094a83c491af96 for (int j = 0; j < i; ++j) { diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java -index 418a7c9217e6e06bff6866deffff3e7822d35a14..506ef97bbc82ed901998460207199b1b3c88ca40 100644 +index 9c2f9f4e2afb2bd4a94ae4dab54ef131466ed69d..d7cd1c26ab7996d932ef26ed5d8843bba8cc41d7 100644 --- a/src/main/java/net/minecraft/world/item/ItemStack.java +++ b/src/main/java/net/minecraft/world/item/ItemStack.java -@@ -1053,7 +1053,7 @@ public final class ItemStack { - int k; +@@ -1102,7 +1102,7 @@ public final class ItemStack implements DataComponentHolder { + ItemAttributeModifiers itemattributemodifiers = (ItemAttributeModifiers) this.getOrDefault(DataComponents.ATTRIBUTE_MODIFIERS, ItemAttributeModifiers.EMPTY); - if (ItemStack.shouldShowInTooltip(i, ItemStack.TooltipPart.MODIFIERS)) { + if (itemattributemodifiers.showInTooltip()) { - EquipmentSlot[] aenumitemslot = EquipmentSlot.values(); + EquipmentSlot[] aenumitemslot = EquipmentSlot.VALUES; // Leaves - reduce array allocations + int i = aenumitemslot.length; - k = aenumitemslot.length; - + for (int j = 0; j < i; ++j) { diff --git a/src/main/java/net/minecraft/world/item/crafting/ShapedRecipe.java b/src/main/java/net/minecraft/world/item/crafting/ShapedRecipe.java -index d772cf80fa3831e1c79d601ea09a073da089e2c5..4699f5fd794a7ef1f430e801bd35f1c62452aa8e 100644 +index 482d7b12b80328fba97a01bcfeb974b7ac4bcdb7..c3abc0b4ae34e8253272a8312ec05abb480cacab 100644 --- a/src/main/java/net/minecraft/world/item/crafting/ShapedRecipe.java +++ b/src/main/java/net/minecraft/world/item/crafting/ShapedRecipe.java -@@ -16,6 +16,7 @@ import org.bukkit.craftbukkit.inventory.CraftRecipe; +@@ -17,6 +17,7 @@ import org.bukkit.craftbukkit.inventory.CraftRecipe; import org.bukkit.craftbukkit.inventory.CraftShapedRecipe; import org.bukkit.inventory.RecipeChoice; // CraftBukkit end @@ -462,29 +466,40 @@ index d772cf80fa3831e1c79d601ea09a073da089e2c5..4699f5fd794a7ef1f430e801bd35f1c6 public class ShapedRecipe extends io.papermc.paper.inventory.recipe.RecipeBookExactChoiceRecipe implements CraftingRecipe { // Paper - improve exact recipe choices diff --git a/src/main/java/net/minecraft/world/item/enchantment/Enchantments.java b/src/main/java/net/minecraft/world/item/enchantment/Enchantments.java -index 8343e175a7221f7b0e83c39cd5350683a0649f8d..6cff058e409e7f7d1effbf87d7cd17520f0c24b2 100644 +index e158ff1a9dbd054985873e854fcf6c433102059c..e061322fed6adf49a506ea13db50cdf25eb0fb33 100644 --- a/src/main/java/net/minecraft/world/item/enchantment/Enchantments.java +++ b/src/main/java/net/minecraft/world/item/enchantment/Enchantments.java -@@ -63,10 +63,10 @@ public class Enchantments { - public static final Enchantment MULTISHOT = register("multishot", new MultiShotEnchantment(Enchantment.Rarity.RARE, EquipmentSlot.MAINHAND)); - public static final Enchantment QUICK_CHARGE = register("quick_charge", new QuickChargeEnchantment(Enchantment.Rarity.UNCOMMON, EquipmentSlot.MAINHAND)); - public static final Enchantment PIERCING = register("piercing", new ArrowPiercingEnchantment(Enchantment.Rarity.COMMON, EquipmentSlot.MAINHAND)); -- public static final Enchantment MENDING = register("mending", new MendingEnchantment(Enchantment.Rarity.RARE, EquipmentSlot.values())); -- public static final Enchantment VANISHING_CURSE = register( -- "vanishing_curse", new VanishingCurseEnchantment(Enchantment.Rarity.VERY_RARE, EquipmentSlot.values()) -- ); +@@ -303,20 +303,22 @@ public class Enchantments { + public static final Enchantment DENSITY = register("density", new DensityEnchantment()); + public static final Enchantment BREACH = register("breach", new BreachEnchantment()); + public static final Enchantment WIND_BURST = register("wind_burst", new WindBurstEnchantment()); +- public static final Enchantment MENDING = register( + // Leaves start - reduce array allocations -+ public static final Enchantment MENDING = Enchantments.register("mending", new MendingEnchantment(Enchantment.Rarity.RARE, EquipmentSlot.VALUES)); -+ public static final Enchantment VANISHING_CURSE = Enchantments.register("vanishing_curse", new VanishingCurseEnchantment(Enchantment.Rarity.VERY_RARE, EquipmentSlot.VALUES)); ++ public static final Enchantment MENDING = Enchantments.register( + "mending", + new MendingEnchantment( + Enchantment.definition( +- ItemTags.DURABILITY_ENCHANTABLE, 2, 1, Enchantment.dynamicCost(25, 25), Enchantment.dynamicCost(75, 25), 4, EquipmentSlot.values() ++ ItemTags.DURABILITY_ENCHANTABLE, 2, 1, Enchantment.dynamicCost(25, 25), Enchantment.dynamicCost(75, 25), 4, EquipmentSlot.VALUES + ) + ) + ); + public static final Enchantment VANISHING_CURSE = register( + "vanishing_curse", + new VanishingCurseEnchantment( +- Enchantment.definition(ItemTags.VANISHING_ENCHANTABLE, 1, 1, Enchantment.constantCost(25), Enchantment.constantCost(50), 8, EquipmentSlot.values()) ++ Enchantment.definition(ItemTags.VANISHING_ENCHANTABLE, 1, 1, Enchantment.constantCost(25), Enchantment.constantCost(50), 8, EquipmentSlot.VALUES) + ) + ); + // Leaves end - reduce array allocations private static Enchantment register(String name, Enchantment enchantment) { return Registry.register(BuiltInRegistries.ENCHANTMENT, name, enchantment); diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java -index e32f8d7f6804a3f0822e0069822d56a3dc873a6a..b219ff03ae82b9d94ba32304671428cf9987329a 100644 +index 3103aceb4c54322293166a63012268a58f2e601c..6c4947994906b140a4af046f2ff8b01755560703 100644 --- a/src/main/java/net/minecraft/world/level/Level.java +++ b/src/main/java/net/minecraft/world/level/Level.java -@@ -95,6 +95,7 @@ import org.bukkit.craftbukkit.util.CraftSpawnCategory; +@@ -101,6 +101,7 @@ import org.bukkit.craftbukkit.util.CraftSpawnCategory; import org.bukkit.entity.SpawnCategory; import org.bukkit.event.block.BlockPhysicsEvent; // CraftBukkit end @@ -492,7 +507,7 @@ index e32f8d7f6804a3f0822e0069822d56a3dc873a6a..b219ff03ae82b9d94ba32304671428cf public abstract class Level implements LevelAccessor, AutoCloseable { -@@ -1867,7 +1868,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { +@@ -1868,7 +1869,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { public org.bukkit.entity.Entity[] getChunkEntities(int chunkX, int chunkZ) { io.papermc.paper.world.ChunkEntitySlices slices = ((ServerLevel)this).getEntityLookup().getChunk(chunkX, chunkZ); if (slices == null) { @@ -502,10 +517,10 @@ index e32f8d7f6804a3f0822e0069822d56a3dc873a6a..b219ff03ae82b9d94ba32304671428cf return slices.getChunkEntities(); } diff --git a/src/main/java/net/minecraft/world/level/block/ComposterBlock.java b/src/main/java/net/minecraft/world/level/block/ComposterBlock.java -index f9084e2605d7403721fe6b714bfad051f932aaef..07d9620768150444b3cc1e288ab8ad079b213317 100644 +index d3d12f9114173f4971f95d7ef895a4374705bd3f..6992421f4425fa0a92ec8c8308e611d8fe225f68 100644 --- a/src/main/java/net/minecraft/world/level/block/ComposterBlock.java +++ b/src/main/java/net/minecraft/world/level/block/ComposterBlock.java -@@ -44,6 +44,7 @@ import net.minecraft.world.phys.shapes.VoxelShape; +@@ -46,6 +46,7 @@ import net.minecraft.world.phys.shapes.VoxelShape; import org.bukkit.craftbukkit.inventory.CraftBlockInventoryHolder; import org.bukkit.craftbukkit.util.DummyGeneratorAccess; // CraftBukkit end @@ -513,7 +528,7 @@ index f9084e2605d7403721fe6b714bfad051f932aaef..07d9620768150444b3cc1e288ab8ad07 public class ComposterBlock extends Block implements WorldlyContainerHolder { -@@ -422,7 +423,7 @@ public class ComposterBlock extends Block implements WorldlyContainerHolder { +@@ -430,7 +431,7 @@ public class ComposterBlock extends Block implements WorldlyContainerHolder { @Override public int[] getSlotsForFace(Direction side) { @@ -522,7 +537,7 @@ index f9084e2605d7403721fe6b714bfad051f932aaef..07d9620768150444b3cc1e288ab8ad07 } @Override -@@ -471,7 +472,7 @@ public class ComposterBlock extends Block implements WorldlyContainerHolder { +@@ -479,7 +480,7 @@ public class ComposterBlock extends Block implements WorldlyContainerHolder { @Override public int[] getSlotsForFace(Direction side) { @@ -531,7 +546,7 @@ index f9084e2605d7403721fe6b714bfad051f932aaef..07d9620768150444b3cc1e288ab8ad07 } @Override -@@ -513,7 +514,7 @@ public class ComposterBlock extends Block implements WorldlyContainerHolder { +@@ -521,7 +522,7 @@ public class ComposterBlock extends Block implements WorldlyContainerHolder { @Override public int[] getSlotsForFace(Direction side) { @@ -541,10 +556,10 @@ index f9084e2605d7403721fe6b714bfad051f932aaef..07d9620768150444b3cc1e288ab8ad07 @Override diff --git a/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java b/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java -index cb0cb894df65e6d4d65b369955a7d7d78fa7bc2b..178c897630e208b4d86615e8f6759ca35e49dae6 100644 +index a99fe191c429bb528209dd0f31b509acf9cccbb5..b45078748be3ab7438b7b25d17233e91d5fc848f 100644 --- a/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java +++ b/src/main/java/net/minecraft/world/level/block/entity/AbstractFurnaceBlockEntity.java -@@ -59,6 +59,7 @@ import org.bukkit.event.inventory.FurnaceSmeltEvent; +@@ -60,6 +60,7 @@ import org.bukkit.event.inventory.FurnaceSmeltEvent; import org.bukkit.event.inventory.FurnaceStartSmeltEvent; import org.bukkit.inventory.CookingRecipe; // CraftBukkit end @@ -552,7 +567,7 @@ index cb0cb894df65e6d4d65b369955a7d7d78fa7bc2b..178c897630e208b4d86615e8f6759ca3 public abstract class AbstractFurnaceBlockEntity extends BaseContainerBlockEntity implements WorldlyContainer, RecipeCraftingHolder, StackedContentsCompatible { -@@ -66,7 +67,7 @@ public abstract class AbstractFurnaceBlockEntity extends BaseContainerBlockEntit +@@ -67,7 +68,7 @@ public abstract class AbstractFurnaceBlockEntity extends BaseContainerBlockEntit protected static final int SLOT_FUEL = 1; protected static final int SLOT_RESULT = 2; public static final int DATA_LIT_TIME = 0; @@ -562,39 +577,26 @@ index cb0cb894df65e6d4d65b369955a7d7d78fa7bc2b..178c897630e208b4d86615e8f6759ca3 private static final int[] SLOTS_FOR_SIDES = new int[]{1}; public static final int DATA_LIT_DURATION = 1; diff --git a/src/main/java/net/minecraft/world/level/storage/PlayerDataStorage.java b/src/main/java/net/minecraft/world/level/storage/PlayerDataStorage.java -index 63e187c65cb855031f286aad0d25ac4694f7a331..8d73357f0120ada1634f18ded0f72218e533776b 100644 +index 8ab7ca373a885fbe658013c9c6a2e38d32d77bb2..0a8666ccdd9942da5e59361656df095b1638cc98 100644 --- a/src/main/java/net/minecraft/world/level/storage/PlayerDataStorage.java +++ b/src/main/java/net/minecraft/world/level/storage/PlayerDataStorage.java -@@ -12,7 +12,6 @@ import net.minecraft.nbt.NbtAccounter; +@@ -16,10 +16,10 @@ import net.minecraft.nbt.NbtAccounter; import net.minecraft.nbt.NbtIo; import net.minecraft.nbt.NbtUtils; import net.minecraft.server.level.ServerPlayer; -import net.minecraft.util.datafix.DataFixTypes; import net.minecraft.world.entity.player.Player; - import org.slf4j.Logger; - -@@ -21,6 +20,7 @@ import java.io.FileInputStream; - import java.io.InputStream; import org.bukkit.craftbukkit.entity.CraftPlayer; - // CraftBukkit end + import org.slf4j.Logger; +import top.leavesmc.leaves.util.ArrayConstants; public class PlayerDataStorage { -@@ -124,7 +124,7 @@ public class PlayerDataStorage { - String[] astring = this.playerDir.list(); - - if (astring == null) { -- astring = new String[0]; -+ astring = ArrayConstants.emptyStringArray; // Leaves - reduce array allocations - } - - for (int i = 0; i < astring.length; ++i) { diff --git a/src/main/java/org/bukkit/craftbukkit/CraftEquipmentSlot.java b/src/main/java/org/bukkit/craftbukkit/CraftEquipmentSlot.java -index 402a238cf502003a232bb95473bd13e59e067fab..6095f5c9298558c77a788c1c9f9ef1f32825b37c 100644 +index ae86c45c1d49c7646c721991910592091e7333f8..f3dce7156d518193fe27a69f5792800b72742632 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftEquipmentSlot.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftEquipmentSlot.java -@@ -5,8 +5,10 @@ import org.bukkit.inventory.EquipmentSlot; +@@ -7,8 +7,10 @@ import org.bukkit.inventory.EquipmentSlot; public class CraftEquipmentSlot { @@ -641,30 +643,3 @@ index b25dc23b81687dd4d4e70b3615ffb91f8c03c68b..9435ea991a429c1d81d4cba63231e846 } @Override -diff --git a/src/main/java/top/leavesmc/leaves/util/ArrayConstants.java b/src/main/java/top/leavesmc/leaves/util/ArrayConstants.java -new file mode 100644 -index 0000000000000000000000000000000000000000..0523062a825bd36f335f1fa6e1440eaaf400fd58 ---- /dev/null -+++ b/src/main/java/top/leavesmc/leaves/util/ArrayConstants.java -@@ -0,0 +1,21 @@ -+package top.leavesmc.leaves.util; -+ -+import net.minecraft.server.level.ServerLevel; -+ -+// Powered by Gale(https://github.com/GaleMC/Gale) -+ -+public class ArrayConstants { -+ -+ private ArrayConstants() {} -+ -+ public static final Object[] emptyObjectArray = new Object[0]; -+ public static final int[] emptyIntArray = new int[0]; -+ public static final int[] zeroSingletonIntArray = new int[]{0}; -+ public static final byte[] emptyByteArray = new byte[0]; -+ public static final String[] emptyStringArray = new String[0]; -+ public static final long[] emptyLongArray = new long[0]; -+ public static final org.bukkit.entity.Entity[] emptyBukkitEntityArray = new org.bukkit.entity.Entity[0]; -+ public static final net.minecraft.world.entity.Entity[] emptyEntityArray = new net.minecraft.world.entity.Entity[0]; -+ public static final ServerLevel[] emptyServerLevelArray = new ServerLevel[0]; -+ -+} diff --git a/patches/server/0089-Optimize-sun-burn-tick.patch b/patches/server/0089-Optimize-sun-burn-tick.patch index d6c629ff..a35967c8 100644 --- a/patches/server/0089-Optimize-sun-burn-tick.patch +++ b/patches/server/0089-Optimize-sun-burn-tick.patch @@ -6,10 +6,10 @@ Subject: [PATCH] Optimize sun burn tick This patch is Powered by Gale(https://github.com/GaleMC/Gale) diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index 4b0e6e5366294f74c24a3bbd97699ede15706954..8b5c464eed35364edb441012ce5e7ecf8f03e004 100644 +index 575252c43504addaf1561fdfd3152ec8f802fb08..52beb04d26bf8d48a9290fcb82a49b169d959923 100644 --- a/src/main/java/net/minecraft/world/entity/Entity.java +++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -2036,8 +2036,22 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, S +@@ -2085,8 +2085,22 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess /** @deprecated */ @Deprecated public float getLightLevelDependentMagicValue() { @@ -34,11 +34,11 @@ index 4b0e6e5366294f74c24a3bbd97699ede15706954..8b5c464eed35364edb441012ce5e7ecf public void absMoveTo(double x, double y, double z, float yaw, float pitch) { this.absMoveTo(x, y, z); diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java -index a4a0f99784ba5a0af851ff43eaf4ca10b6e96b1d..f41ed311f9368a0ab81a613238cd5a2468aae706 100644 +index 588e6cb991d8a58d067f391e41f10834454113f8..407cf921219916ce8050ff4bdc8fcf37b1130e41 100644 --- a/src/main/java/net/minecraft/world/entity/Mob.java +++ b/src/main/java/net/minecraft/world/entity/Mob.java -@@ -1725,15 +1725,41 @@ public abstract class Mob extends LivingEntity implements Targeting { - +@@ -1845,15 +1845,41 @@ public abstract class Mob extends LivingEntity implements EquipmentUser, Targeti + return flag; } + // Leaves start - optimize sun burn tick @@ -56,9 +56,7 @@ index a4a0f99784ba5a0af851ff43eaf4ca10b6e96b1d..f41ed311f9368a0ab81a613238cd5a24 + float f = this.getLightLevelDependentMagicValue(); + BlockPos blockposition = BlockPos.containing(this.getX(), this.getEyeY(), this.getZ()); + boolean flag = this.isInWaterRainOrBubble() || this.isInPowderSnow || this.wasInPowderSnow; - -- if (f > 0.5F && this.random.nextFloat() * 30.0F < (f - 0.4F) * 2.0F && !flag && this.level().canSeeSky(blockposition)) { -- return true; ++ + if (f > 0.5F && this.random.nextFloat() * 30.0F < (f - 0.4F) * 2.0F && !flag && this.level().canSeeSky(blockposition)) { + return true; + } @@ -75,7 +73,9 @@ index a4a0f99784ba5a0af851ff43eaf4ca10b6e96b1d..f41ed311f9368a0ab81a613238cd5a24 + if (f <= 0.5F) return false; + if (this.random.nextFloat() * 30.0F >= (f - 0.4F) * 2.0F) return false; + boolean flag = this.isInWaterRainOrBubble() || this.isInPowderSnow || this.wasInPowderSnow; -+ + +- if (f > 0.5F && this.random.nextFloat() * 30.0F < (f - 0.4F) * 2.0F && !flag && this.level().canSeeSky(blockposition)) { +- return true; + if (!flag && this.level().canSeeSky(this.cached_eye_blockpos)) { + return true; + } diff --git a/patches/server/0051-Leaves-Extra-Yggdrasil-Service.patch b/patches/unapplied/server/0051-Leaves-Extra-Yggdrasil-Service.patch similarity index 100% rename from patches/server/0051-Leaves-Extra-Yggdrasil-Service.patch rename to patches/unapplied/server/0051-Leaves-Extra-Yggdrasil-Service.patch diff --git a/patches/server/0054-Bedrock-break-list.patch b/patches/unapplied/server/0054-Bedrock-break-list.patch similarity index 100% rename from patches/server/0054-Bedrock-break-list.patch rename to patches/unapplied/server/0054-Bedrock-break-list.patch diff --git a/patches/server/0063-Leaves-carpet-support.patch b/patches/unapplied/server/0063-Leaves-carpet-support.patch similarity index 100% rename from patches/server/0063-Leaves-carpet-support.patch rename to patches/unapplied/server/0063-Leaves-carpet-support.patch diff --git a/patches/server/0069-Elytra-aeronautics-no-chunk-load.patch b/patches/unapplied/server/0069-Elytra-aeronautics-no-chunk-load.patch similarity index 100% rename from patches/server/0069-Elytra-aeronautics-no-chunk-load.patch rename to patches/unapplied/server/0069-Elytra-aeronautics-no-chunk-load.patch diff --git a/patches/unapplied/server/0106-Replay-Mod-API.patch b/patches/unapplied/server/0106-Replay-Mod-API.patch new file mode 100644 index 00000000..470125aa --- /dev/null +++ b/patches/unapplied/server/0106-Replay-Mod-API.patch @@ -0,0 +1,1547 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: violetc <58360096+s-yh-china@users.noreply.github.com> +Date: Thu, 3 Aug 2023 20:36:38 +0800 +Subject: [PATCH] Replay Mod API + +This patch is Powered by ReplayMod(https://github.com/ReplayMod) + +diff --git a/src/main/java/net/minecraft/commands/arguments/EntityArgument.java b/src/main/java/net/minecraft/commands/arguments/EntityArgument.java +index 8d79cfa371546996ef65f94232c1d344e7c590ec..9c262c82d9ab24bdbbe03df8cee3d5d99e8f8868 100644 +--- a/src/main/java/net/minecraft/commands/arguments/EntityArgument.java ++++ b/src/main/java/net/minecraft/commands/arguments/EntityArgument.java +@@ -147,6 +147,7 @@ public class EntityArgument implements ArgumentType { + if (icompletionprovider instanceof CommandSourceStack commandSourceStack && commandSourceStack.getEntity() instanceof ServerPlayer sourcePlayer) { + collection = new java.util.ArrayList<>(); + for (final ServerPlayer player : commandSourceStack.getServer().getPlayerList().getPlayers()) { ++ if (player instanceof top.leavesmc.leaves.replay.ServerPhotographer) continue; // Leaves - skip photographer + if (sourcePlayer.getBukkitEntity().canSee(player.getBukkitEntity())) { + collection.add(player.getGameProfile().getName()); + } +diff --git a/src/main/java/net/minecraft/commands/arguments/selector/EntitySelector.java b/src/main/java/net/minecraft/commands/arguments/selector/EntitySelector.java +index 676a1499747b071515479130875157263d3a8352..e5ef298dc1df9cc42b3d349939a966b77fc0d554 100644 +--- a/src/main/java/net/minecraft/commands/arguments/selector/EntitySelector.java ++++ b/src/main/java/net/minecraft/commands/arguments/selector/EntitySelector.java +@@ -122,6 +122,7 @@ public class EntitySelector { + return this.findPlayers(source); + } else if (this.playerName != null) { + ServerPlayer entityplayer = source.getServer().getPlayerList().getPlayerByName(this.playerName); ++ entityplayer = entityplayer instanceof top.leavesmc.leaves.replay.ServerPhotographer ? null : entityplayer; // Leaves - skip photographer + + return (List) (entityplayer == null ? Collections.emptyList() : Lists.newArrayList(new ServerPlayer[]{entityplayer})); + } else if (this.entityUUID != null) { +@@ -137,6 +138,7 @@ public class EntitySelector { + ServerLevel worldserver = (ServerLevel) iterator.next(); + + entity = worldserver.getEntity(this.entityUUID); ++ entity = entity instanceof top.leavesmc.leaves.replay.ServerPhotographer ? null : entity; // Leaves - skip photographer + } while (entity == null); + + return Lists.newArrayList(new Entity[]{entity}); +@@ -145,7 +147,7 @@ public class EntitySelector { + Predicate predicate = this.getPredicate(vec3d); + + if (this.currentEntity) { +- return (List) (source.getEntity() != null && predicate.test(source.getEntity()) ? Lists.newArrayList(new Entity[]{source.getEntity()}) : Collections.emptyList()); ++ return (List) (source.getEntity() != null && !(source.getEntity() instanceof top.leavesmc.leaves.replay.ServerPhotographer) && predicate.test(source.getEntity()) ? Lists.newArrayList(new Entity[]{source.getEntity()}) : Collections.emptyList()); // Leaves - skip photographer + } else { + List list = Lists.newArrayList(); + +@@ -160,6 +162,7 @@ public class EntitySelector { + this.addEntities(list, worldserver1, vec3d, predicate); + } + } ++ list.removeIf(entity -> entity instanceof top.leavesmc.leaves.replay.ServerPhotographer); // Leaves - skip photographer + + return this.sortAndLimit(vec3d, list); + } +@@ -200,9 +203,11 @@ public class EntitySelector { + + if (this.playerName != null) { + entityplayer = source.getServer().getPlayerList().getPlayerByName(this.playerName); ++ entityplayer = entityplayer instanceof top.leavesmc.leaves.replay.ServerPhotographer ? null : entityplayer; // Leaves - skip photographer + return (List) (entityplayer == null ? Collections.emptyList() : Lists.newArrayList(new ServerPlayer[]{entityplayer})); + } else if (this.entityUUID != null) { + entityplayer = source.getServer().getPlayerList().getPlayer(this.entityUUID); ++ entityplayer = entityplayer instanceof top.leavesmc.leaves.replay.ServerPhotographer ? null : entityplayer; // Leaves - skip photographer + return (List) (entityplayer == null ? Collections.emptyList() : Lists.newArrayList(new ServerPlayer[]{entityplayer})); + } else { + Vec3 vec3d = (Vec3) this.position.apply(source.getPosition()); +@@ -214,7 +219,7 @@ public class EntitySelector { + if (entity instanceof ServerPlayer) { + ServerPlayer entityplayer1 = (ServerPlayer) entity; + +- if (predicate.test(entityplayer1)) { ++ if (predicate.test(entityplayer1) && !(entityplayer1 instanceof top.leavesmc.leaves.replay.ServerPhotographer)) { // Leaves - skip photographer + return Lists.newArrayList(new ServerPlayer[]{entityplayer1}); + } + } +@@ -225,7 +230,7 @@ public class EntitySelector { + Object object; + + if (this.isWorldLimited()) { +- object = source.getLevel().getPlayers(predicate, i); ++ object = source.getLevel().getPlayers((entityplayer3 -> !(entityplayer3 instanceof top.leavesmc.leaves.replay.ServerPhotographer) && predicate.test(entityplayer3)), i); // Leaves - skip photographer + } else { + object = Lists.newArrayList(); + Iterator iterator = source.getServer().getPlayerList().getPlayers().iterator(); +@@ -233,7 +238,7 @@ public class EntitySelector { + while (iterator.hasNext()) { + ServerPlayer entityplayer2 = (ServerPlayer) iterator.next(); + +- if (predicate.test(entityplayer2)) { ++ if (predicate.test(entityplayer2) && !(entityplayer2 instanceof top.leavesmc.leaves.replay.ServerPhotographer)) { // Leaves - skip photographer + ((List) object).add(entityplayer2); + if (((List) object).size() >= i) { + return (List) object; +diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java +index 791e6680db77d3ec464168e5225d3ab0844b5e84..7fd45603c33730e3cb8d3aa1180ecd30f253e0f5 100644 +--- a/src/main/java/net/minecraft/server/MinecraftServer.java ++++ b/src/main/java/net/minecraft/server/MinecraftServer.java +@@ -1606,7 +1606,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop list = this.playerList.getPlayers(); ++ List list = this.playerList.realPlayers; // Leaves - only real player + int i = this.getMaxPlayers(); + + if (this.hidesOnlinePlayers()) { +diff --git a/src/main/java/net/minecraft/server/PlayerAdvancements.java b/src/main/java/net/minecraft/server/PlayerAdvancements.java +index cb05ec292d26320562e51da96a5a28cb04ecc7e4..6cf0b94b185d901ee79f376a6d2ffd8ccfae5b84 100644 +--- a/src/main/java/net/minecraft/server/PlayerAdvancements.java ++++ b/src/main/java/net/minecraft/server/PlayerAdvancements.java +@@ -223,7 +223,7 @@ public class PlayerAdvancements { + + public boolean award(AdvancementHolder advancement, String criterionName) { + // Leaves start - bot can't get advancement +- if (player instanceof top.leavesmc.leaves.bot.ServerBot) { ++ if (player instanceof top.leavesmc.leaves.bot.ServerBot || player instanceof top.leavesmc.leaves.replay.ServerPhotographer) { // Leaves - and photographer + return false; + } + // Leaves end - bot can't get advancement +diff --git a/src/main/java/net/minecraft/server/commands/OpCommand.java b/src/main/java/net/minecraft/server/commands/OpCommand.java +index e7b444a10b244828827b3c66c53465206ea8e0ec..030601fdfde2232a933b2ad7022e9909db3a3783 100644 +--- a/src/main/java/net/minecraft/server/commands/OpCommand.java ++++ b/src/main/java/net/minecraft/server/commands/OpCommand.java +@@ -25,7 +25,7 @@ public class OpCommand { + (context, builder) -> { + PlayerList playerList = context.getSource().getServer().getPlayerList(); + return SharedSuggestionProvider.suggest( +- playerList.getPlayers() ++ playerList.realPlayers // Leaves - only real player + .stream() + .filter(player -> !playerList.isOp(player.getGameProfile())) + .map(player -> player.getGameProfile().getName()), +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 2730718b162c20110145798dbb0eaad0b139e99b..7f8b5195ac50f153411eb6713c6a861864cc8886 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -299,7 +299,7 @@ public class ServerGamePacketListenerImpl extends ServerCommonPacketListenerImpl + super(server, connection, clientData, player); // CraftBukkit + this.chunkSender = new PlayerChunkSender(connection.isMemoryConnection()); + // Leaves start - fakeplayer +- if (player instanceof top.leavesmc.leaves.bot.ServerBot) { ++ if (player instanceof top.leavesmc.leaves.bot.ServerBot || player instanceof top.leavesmc.leaves.replay.ServerPhotographer) { // Leaves - and photographer + connection.setListenerForce(this); + } else { + connection.setListener(this); +diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java +index 139129f0110dc656bc729ce795f012ec087641cf..dc140b2003a997c13f7bdc8f5c707f98f4d6ff17 100644 +--- a/src/main/java/net/minecraft/server/players/PlayerList.java ++++ b/src/main/java/net/minecraft/server/players/PlayerList.java +@@ -124,6 +124,7 @@ import org.bukkit.event.player.PlayerSpawnChangeEvent; + import top.leavesmc.leaves.bot.ServerBot; + import top.leavesmc.leaves.util.ReturnPortalManager; // Leaves - return portal fix + import top.leavesmc.leaves.util.ArrayConstants; ++import top.leavesmc.leaves.replay.ServerPhotographer; + + public abstract class PlayerList { + +@@ -156,6 +157,7 @@ public abstract class PlayerList { + private boolean allowCheatsForAllPlayers; + private static final boolean ALLOW_LOGOUTIVATOR = false; + private int sendAllPlayerInfoIn; ++ public final List realPlayers = new java.util.concurrent.CopyOnWriteArrayList(); // Leaves - replay api + + // CraftBukkit start + private CraftServer cserver; +@@ -182,6 +184,120 @@ public abstract class PlayerList { + } + abstract public void loadAndSaveFiles(); // Paper - fix converting txt to json file; moved from DedicatedPlayerList constructor + ++ // Leaves start - replay api ++ public void placeNewPhotographer(Connection connection, ServerPhotographer player, ServerLevel worldserver, Location location) { ++ player.isRealPlayer = true; // Paper ++ player.loginTime = System.currentTimeMillis(); // Paper ++ ++ ServerLevel worldserver1 = worldserver; ++ ++ player.setServerLevel(worldserver1); ++ player.spawnIn(worldserver1); ++ player.gameMode.setLevel((ServerLevel) player.level()); ++ ++ LevelData worlddata = worldserver1.getLevelData(); ++ ++ player.loadGameTypes(null); ++ ServerGamePacketListenerImpl playerconnection = new ServerGamePacketListenerImpl(this.server, connection, player, CommonListenerCookie.createInitial(player.gameProfile)); ++ GameRules gamerules = worldserver1.getGameRules(); ++ boolean flag = gamerules.getBoolean(GameRules.RULE_DO_IMMEDIATE_RESPAWN); ++ boolean flag1 = gamerules.getBoolean(GameRules.RULE_REDUCEDDEBUGINFO); ++ boolean flag2 = gamerules.getBoolean(GameRules.RULE_LIMITED_CRAFTING); ++ ++ playerconnection.send(new ClientboundLoginPacket(player.getId(), worlddata.isHardcore(), this.server.levelKeys(), this.getMaxPlayers(), worldserver1.getWorld().getSendViewDistance(), worldserver1.getWorld().getSimulationDistance(), flag1, !flag, flag2, player.createCommonSpawnInfo(worldserver1))); // Paper - replace old player chunk management ++ player.getBukkitEntity().sendSupportedChannels(); // CraftBukkit ++ playerconnection.send(new ClientboundChangeDifficultyPacket(worlddata.getDifficulty(), worlddata.isDifficultyLocked())); ++ playerconnection.send(new ClientboundPlayerAbilitiesPacket(player.getAbilities())); ++ playerconnection.send(new ClientboundSetCarriedItemPacket(player.getInventory().selected)); ++ playerconnection.send(new ClientboundUpdateRecipesPacket(this.server.getRecipeManager().getRecipes())); ++ this.sendPlayerPermissionLevel(player); ++ player.getStats().markAllDirty(); ++ player.getRecipeBook().sendInitialRecipeBook(player); ++ this.updateEntireScoreboard(worldserver1.getScoreboard(), player); ++ this.server.invalidateStatus(); ++ ++ playerconnection.teleport(player.getX(), player.getY(), player.getZ(), player.getYRot(), player.getXRot()); ++ ServerStatus serverping = this.server.getStatus(); ++ ++ if (serverping != null) { ++ player.sendServerStatus(serverping); ++ } ++ ++ this.players.add(player); ++ this.playersByName.put(player.getScoreboardName().toLowerCase(java.util.Locale.ROOT), player); // Spigot ++ this.playersByUUID.put(player.getUUID(), player); ++ ++ player.supressTrackerForLogin = true; ++ worldserver1.addNewPlayer(player); ++ this.server.getCustomBossEvents().onPlayerConnect(player); ++ mountSavedVehicle(player, worldserver1, null); ++ CraftPlayer bukkitPlayer = player.getBukkitEntity(); ++ ++ player.containerMenu.transferTo(player.containerMenu, bukkitPlayer); ++ if (!player.connection.isAcceptingMessages()) { ++ return; ++ } ++ ++ top.leavesmc.leaves.protocol.core.LeavesProtocolManager.handlePlayerJoin(player); // Leaves - protocol ++ ++ // Leaves start - bot support ++ if (top.leavesmc.leaves.LeavesConfig.fakeplayerSupport) { ++ ServerBot bot = ServerBot.getBot(player.getScoreboardName().toLowerCase(java.util.Locale.ROOT)); ++ if (bot != null) { ++ bot.die(bot.damageSources().fellOutOfWorld()); // Leaves - remove bot with the same name ++ this.playersByName.put(player.getScoreboardName().toLowerCase(java.util.Locale.ROOT), player); ++ this.playersByUUID.put(player.getUUID(), player); ++ } ++ ServerBot.getBots().forEach(bot1 -> { ++ bot1.sendPlayerInfo(player); ++ bot1.sendFakeDataIfNeed(player, true); ++ }); // Leaves - render bot ++ } ++ // Leaves end - bot support ++ ++ final List onlinePlayers = Lists.newArrayListWithExpectedSize(this.players.size() - 1); ++ for (int i = 0; i < this.players.size(); ++i) { ++ ServerPlayer entityplayer1 = this.players.get(i); ++ ++ if (entityplayer1 == player || !bukkitPlayer.canSee(entityplayer1.getBukkitEntity())) { ++ continue; ++ } ++ ++ onlinePlayers.add(entityplayer1); ++ } ++ if (!onlinePlayers.isEmpty()) { ++ player.connection.send(ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(onlinePlayers, player)); ++ } ++ ++ player.sentListPacket = true; ++ player.supressTrackerForLogin = false; ++ ((ServerLevel)player.level()).getChunkSource().chunkMap.addEntity(player); ++ ++ this.sendLevelInfo(player, worldserver1); ++ ++ if (player.level() == worldserver1 && !worldserver1.players().contains(player)) { ++ worldserver1.addNewPlayer(player); ++ this.server.getCustomBossEvents().onPlayerConnect(player); ++ } ++ ++ worldserver1 = player.serverLevel(); ++ Iterator iterator = player.getActiveEffects().iterator(); ++ while (iterator.hasNext()) { ++ MobEffectInstance mobeffect = (MobEffectInstance) iterator.next(); ++ playerconnection.send(new ClientboundUpdateMobEffectPacket(player.getId(), mobeffect)); ++ } ++ ++ if (player.isDeadOrDying()) { ++ net.minecraft.core.Holder plains = worldserver1.registryAccess().registryOrThrow(net.minecraft.core.registries.Registries.BIOME) ++ .getHolderOrThrow(net.minecraft.world.level.biome.Biomes.PLAINS); ++ player.connection.send(new net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket( ++ new net.minecraft.world.level.chunk.EmptyLevelChunk(worldserver1, player.chunkPosition(), plains), ++ worldserver1.getLightEngine(), (java.util.BitSet)null, (java.util.BitSet) null, false) ++ ); ++ } ++ } ++ // Leaves end - replay api ++ + public void placeNewPlayer(Connection connection, ServerPlayer player, CommonListenerCookie clientData) { + player.isRealPlayer = true; // Paper + player.loginTime = System.currentTimeMillis(); // Paper - Replace OfflinePlayer#getLastPlayed +@@ -328,6 +444,7 @@ public abstract class PlayerList { + + // entityplayer.connection.send(ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(this.players)); // CraftBukkit - replaced with loop below + this.players.add(player); ++ this.realPlayers.add(player); // Leaves - replay api + this.playersByName.put(player.getScoreboardName().toLowerCase(java.util.Locale.ROOT), player); // Spigot + this.playersByUUID.put(player.getUUID(), player); + // this.broadcastAll(ClientboundPlayerInfoUpdatePacket.createPlayerInitializing(List.of(entityplayer))); // CraftBukkit - replaced with loop below +@@ -399,6 +516,12 @@ public abstract class PlayerList { + continue; + } + ++ // Leaves start - skip photographer ++ if (entityplayer1 instanceof ServerPhotographer) { ++ continue; ++ } ++ // Leaves end - skip photographer ++ + onlinePlayers.add(entityplayer1); // Paper - Use single player info update packet on join + } + // Paper start - Use single player info update packet on join +@@ -613,6 +736,43 @@ public abstract class PlayerList { + + } + ++ // Leaevs start - replay mod api ++ public void removePhotographer(ServerPhotographer entityplayer) { ++ ServerLevel worldserver = entityplayer.serverLevel(); ++ ++ entityplayer.awardStat(Stats.LEAVE_GAME); ++ ++ if (entityplayer.containerMenu != entityplayer.inventoryMenu) { ++ entityplayer.closeContainer(org.bukkit.event.inventory.InventoryCloseEvent.Reason.DISCONNECT); ++ } ++ ++ if (server.isSameThread()) entityplayer.doTick(); ++ ++ if (this.collideRuleTeamName != null) { ++ final net.minecraft.world.scores.Scoreboard scoreBoard = this.server.getLevel(Level.OVERWORLD).getScoreboard(); ++ final PlayerTeam team = scoreBoard.getPlayersTeam(this.collideRuleTeamName); ++ if (entityplayer.getTeam() == team && team != null) { ++ scoreBoard.removePlayerFromTeam(entityplayer.getScoreboardName(), team); ++ } ++ } ++ ++ worldserver.removePlayerImmediately(entityplayer, Entity.RemovalReason.UNLOADED_WITH_PLAYER); ++ entityplayer.retireScheduler(); ++ entityplayer.getAdvancements().stopListening(); ++ this.players.remove(entityplayer); ++ this.playersByName.remove(entityplayer.getScoreboardName().toLowerCase(java.util.Locale.ROOT)); ++ this.server.getCustomBossEvents().onPlayerDisconnect(entityplayer); ++ UUID uuid = entityplayer.getUUID(); ++ ServerPlayer entityplayer1 = this.playersByUUID.get(uuid); ++ ++ if (entityplayer1 == entityplayer) { ++ this.playersByUUID.remove(uuid); ++ } ++ ++ this.cserver.getScoreboardManager().removePlayer(entityplayer.getBukkitEntity()); ++ } ++ // Leaves stop - replay mod api ++ + public net.kyori.adventure.text.Component remove(ServerPlayer entityplayer) { // CraftBukkit - return string // Paper - return Component + // Paper start - Fix kick event leave message not being sent + return this.remove(entityplayer, net.kyori.adventure.text.Component.translatable("multiplayer.player.left", net.kyori.adventure.text.format.NamedTextColor.YELLOW, io.papermc.paper.configuration.GlobalConfiguration.get().messages.useDisplayNameInQuitMessage ? entityplayer.getBukkitEntity().displayName() : io.papermc.paper.adventure.PaperAdventure.asAdventure(entityplayer.getDisplayName()))); +@@ -681,6 +841,7 @@ public abstract class PlayerList { + entityplayer.retireScheduler(); // Paper - Folia schedulers + entityplayer.getAdvancements().stopListening(); + this.players.remove(entityplayer); ++ this.realPlayers.remove(entityplayer); // Leaves - replay api + this.playersByName.remove(entityplayer.getScoreboardName().toLowerCase(java.util.Locale.ROOT)); // Spigot + this.server.getCustomBossEvents().onPlayerDisconnect(entityplayer); + UUID uuid = entityplayer.getUUID(); +@@ -774,7 +935,7 @@ public abstract class PlayerList { + event.disallow(PlayerLoginEvent.Result.KICK_BANNED, io.papermc.paper.adventure.PaperAdventure.asAdventure(ichatmutablecomponent)); // Paper - Adventure + } else { + // return this.players.size() >= this.maxPlayers && !this.canBypassPlayerLimit(gameprofile) ? IChatBaseComponent.translatable("multiplayer.disconnect.server_full") : null; +- if (this.players.size() >= this.maxPlayers && !this.canBypassPlayerLimit(gameprofile)) { ++ if (this.realPlayers.size() >= this.maxPlayers && !this.canBypassPlayerLimit(gameprofile)) { // Leaves - only real player + event.disallow(PlayerLoginEvent.Result.KICK_FULL, net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(org.spigotmc.SpigotConfig.serverFullMessage)); // Spigot // Paper - Adventure + } + } +diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +index 79c10c26e9a27b120070b408e261a26bb66a5082..70b4f5dc96e285184bd3e676397c6bddd193db37 100644 +--- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java ++++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java +@@ -262,6 +262,7 @@ import org.yaml.snakeyaml.error.MarkedYAMLException; + + import net.md_5.bungee.api.chat.BaseComponent; // Spigot + import top.leavesmc.leaves.entity.CraftBotManager; ++import top.leavesmc.leaves.entity.CraftPhotographerManager; + + import javax.annotation.Nullable; // Paper + import javax.annotation.Nonnull; // Paper +@@ -309,6 +310,7 @@ public final class CraftServer implements Server { + private final io.papermc.paper.logging.SysoutCatcher sysoutCatcher = new io.papermc.paper.logging.SysoutCatcher(); // Paper + private final CraftPotionBrewer potionBrewer = new CraftPotionBrewer(); // Paper - Custom Potion Mixes + private final CraftBotManager botManager = new CraftBotManager(); ++ private final CraftPhotographerManager photographerManager = new CraftPhotographerManager(); + + // Paper start - Folia region threading API + private final io.papermc.paper.threadedregions.scheduler.FallbackRegionScheduler regionizedScheduler = new io.papermc.paper.threadedregions.scheduler.FallbackRegionScheduler(); +@@ -390,7 +392,7 @@ public final class CraftServer implements Server { + public CraftServer(DedicatedServer console, PlayerList playerList) { + this.console = console; + this.playerList = (DedicatedPlayerList) playerList; +- this.playerView = Collections.unmodifiableList(Lists.transform(playerList.players, new Function() { ++ this.playerView = Collections.unmodifiableList(Lists.transform(playerList.realPlayers, new Function() { // Leaves - replay api + @Override + public CraftPlayer apply(ServerPlayer player) { + return player.getBukkitEntity(); +@@ -3279,4 +3281,11 @@ public final class CraftServer implements Server { + return botManager; + } + // Leaves end - Bot API ++ ++ // Leaves start - replay mod api ++ @Override ++ public CraftPhotographerManager getPhotographerManager() { ++ return photographerManager; ++ } ++ // Leaves end - replay mod api + } +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +index 12f8ed45dc280a9ad7e975b1922cc9309c3d53b8..2254ab17bc087c2a54a15588b908c55efa01522c 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +@@ -59,6 +59,8 @@ import top.leavesmc.leaves.bot.ServerBot; + import top.leavesmc.leaves.entity.CraftBot; + + import net.md_5.bungee.api.chat.BaseComponent; // Spigot ++import top.leavesmc.leaves.entity.CraftPhotographer; ++import top.leavesmc.leaves.replay.ServerPhotographer; + + public abstract class CraftEntity implements org.bukkit.entity.Entity { + private static PermissibleBase perm; +@@ -95,6 +97,7 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { + } + + if (entity instanceof ServerBot) { return new CraftBot(server, (ServerBot) entity); } ++ if (entity instanceof ServerPhotographer) { return new CraftPhotographer(server, (ServerPhotographer) entity); } + + // Special case complex part, since there is no extra entity type for them + if (entity instanceof EnderDragonPart complexPart) { +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index 44f4665db613c558078df5bb49106e4ca5679dfe..be198fa712ced7b61de16bdbc1f13acd2306cb22 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -2138,7 +2138,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + + @Override + public boolean canSee(Player player) { +- return this.canSee((org.bukkit.entity.Entity) player); ++ return !(player instanceof top.leavesmc.leaves.entity.Photographer) && this.canSee((org.bukkit.entity.Entity) player); // Leaves - skip photographer + } + + @Override +diff --git a/src/main/java/top/leavesmc/leaves/entity/CraftPhotographer.java b/src/main/java/top/leavesmc/leaves/entity/CraftPhotographer.java +new file mode 100644 +index 0000000000000000000000000000000000000000..4f58b6623a6b5c726d718ced6ab106af3e665e35 +--- /dev/null ++++ b/src/main/java/top/leavesmc/leaves/entity/CraftPhotographer.java +@@ -0,0 +1,73 @@ ++package top.leavesmc.leaves.entity; ++ ++import net.minecraft.server.level.ServerPlayer; ++import org.bukkit.craftbukkit.CraftServer; ++import org.bukkit.craftbukkit.entity.CraftPlayer; ++import org.bukkit.entity.Player; ++import org.jetbrains.annotations.NotNull; ++import org.jetbrains.annotations.Nullable; ++import top.leavesmc.leaves.replay.ServerPhotographer; ++ ++import java.io.File; ++ ++public class CraftPhotographer extends CraftPlayer implements Photographer { ++ ++ public CraftPhotographer(CraftServer server, ServerPhotographer entity) { ++ super(server, entity); ++ } ++ ++ @Override ++ public void stopRecording() { ++ this.stopRecording(true); ++ } ++ ++ @Override ++ public void stopRecording(boolean async) { ++ this.stopRecording(async, true); ++ } ++ ++ @Override ++ public void stopRecording(boolean async, boolean save) { ++ this.getHandle().remove(async, save); ++ } ++ ++ @Override ++ public void pauseRecording() { ++ this.getHandle().pauseRecording(); ++ } ++ ++ @Override ++ public void resumeRecording() { ++ this.getHandle().resumeRecording(); ++ } ++ ++ @Override ++ public void setRecordFile(@NotNull File file) { ++ this.getHandle().setSaveFile(file); ++ } ++ ++ @Override ++ public void setFollowPlayer(@Nullable Player player) { ++ ServerPlayer serverPlayer = player != null ? ((CraftPlayer) player).getHandle() : null; ++ this.getHandle().setFollowPlayer(serverPlayer); ++ } ++ ++ @Override ++ public @NotNull String getId() { ++ return this.getHandle().createState.id; ++ } ++ ++ @Override ++ public ServerPhotographer getHandle() { ++ return (ServerPhotographer) entity; ++ } ++ ++ public void setHandle(final ServerPhotographer entity) { ++ super.setHandle(entity); ++ } ++ ++ @Override ++ public String toString() { ++ return "CraftPhotographer{" + "name=" + getName() + '}'; ++ } ++} +diff --git a/src/main/java/top/leavesmc/leaves/entity/CraftPhotographerManager.java b/src/main/java/top/leavesmc/leaves/entity/CraftPhotographerManager.java +new file mode 100644 +index 0000000000000000000000000000000000000000..56b41cb3401ea597b48ec98098fe31f4dfb4933d +--- /dev/null ++++ b/src/main/java/top/leavesmc/leaves/entity/CraftPhotographerManager.java +@@ -0,0 +1,84 @@ ++package top.leavesmc.leaves.entity; ++ ++import com.google.common.collect.Lists; ++import org.bukkit.Location; ++import org.bukkit.util.Consumer; ++import org.jetbrains.annotations.NotNull; ++import org.jetbrains.annotations.Nullable; ++import top.leavesmc.leaves.bot.ServerBot; ++import top.leavesmc.leaves.replay.BukkitRecorderOption; ++import top.leavesmc.leaves.replay.RecorderOption; ++import top.leavesmc.leaves.replay.ServerPhotographer; ++ ++import java.util.Collection; ++import java.util.Collections; ++import java.util.UUID; ++ ++public class CraftPhotographerManager implements PhotographerManager { ++ ++ private final Collection photographerViews = Collections.unmodifiableList(Lists.transform(ServerPhotographer.getPhotographers(), ServerPhotographer::getBukkitPlayer)); ++ ++ @Override ++ public @Nullable Photographer getPhotographer(@NotNull UUID uuid) { ++ ServerPhotographer photographer = ServerPhotographer.getPhotographer(uuid); ++ if (photographer != null) { ++ return photographer.getBukkitPlayer(); ++ } ++ return null; ++ } ++ ++ @Override ++ public @Nullable Photographer getPhotographer(@NotNull String id) { ++ ServerPhotographer photographer = ServerPhotographer.getPhotographer(id); ++ if (photographer != null) { ++ return photographer.getBukkitPlayer(); ++ } ++ return null; ++ } ++ ++ @Override ++ public @Nullable Photographer createPhotographer(@NotNull String id, @NotNull Location location) { ++ ServerPhotographer photographer = new ServerPhotographer.PhotographerCreateState(location, id, RecorderOption.createDefaultOption()).createSync(); ++ if (photographer != null) { ++ return photographer.getBukkitPlayer(); ++ } ++ return null; ++ } ++ ++ @Override ++ public @Nullable Photographer createPhotographer(@NotNull String id, @NotNull Location location, @NotNull BukkitRecorderOption recorderOption) { ++ ServerPhotographer photographer = new ServerPhotographer.PhotographerCreateState(location, id, RecorderOption.createFromBukkit(recorderOption)).createSync(); ++ if (photographer != null) { ++ return photographer.getBukkitPlayer(); ++ } ++ return null; ++ } ++ ++ @Override ++ public void removePhotographer(@NotNull String id) { ++ ServerPhotographer photographer = ServerPhotographer.getPhotographer(id); ++ if (photographer != null) { ++ photographer.remove(true); ++ } ++ } ++ ++ @Override ++ public void removePhotographer(@NotNull UUID uuid) { ++ ServerPhotographer photographer = ServerPhotographer.getPhotographer(uuid); ++ if (photographer != null) { ++ photographer.remove(true); ++ } ++ } ++ ++ @Override ++ public void removeAllPhotographers() { ++ for (ServerPhotographer photographer : ServerPhotographer.getPhotographers()) { ++ photographer.remove(true); ++ } ++ } ++ ++ @Override ++ public Collection getPhotographers() { ++ return photographerViews; ++ } ++} +diff --git a/src/main/java/top/leavesmc/leaves/replay/DigestOutputStream.java b/src/main/java/top/leavesmc/leaves/replay/DigestOutputStream.java +new file mode 100644 +index 0000000000000000000000000000000000000000..92a6478b337c4371e61b7e7878b242619398ecdd +--- /dev/null ++++ b/src/main/java/top/leavesmc/leaves/replay/DigestOutputStream.java +@@ -0,0 +1,46 @@ ++package top.leavesmc.leaves.replay; ++ ++import org.jetbrains.annotations.NotNull; ++ ++import java.io.IOException; ++import java.io.OutputStream; ++import java.util.zip.Checksum; ++ ++public class DigestOutputStream extends OutputStream { ++ ++ private final Checksum sum; ++ private final OutputStream out; ++ ++ public DigestOutputStream(OutputStream out, Checksum sum) { ++ this.out = out; ++ this.sum = sum; ++ } ++ ++ @Override ++ public void close() throws IOException { ++ out.close(); ++ } ++ ++ @Override ++ public void flush() throws IOException { ++ out.flush(); ++ } ++ ++ @Override ++ public void write(int b) throws IOException { ++ sum.update(b); ++ out.write(b); ++ } ++ ++ @Override ++ public void write(byte @NotNull [] b) throws IOException { ++ sum.update(b); ++ out.write(b); ++ } ++ ++ @Override ++ public void write(byte @NotNull [] b, int off, int len) throws IOException { ++ sum.update(b, off, len); ++ out.write(b, off, len); ++ } ++} +diff --git a/src/main/java/top/leavesmc/leaves/replay/RecordMetaData.java b/src/main/java/top/leavesmc/leaves/replay/RecordMetaData.java +new file mode 100644 +index 0000000000000000000000000000000000000000..46a86cfce4aa859b8de7c126c22f64a999a4fe7a +--- /dev/null ++++ b/src/main/java/top/leavesmc/leaves/replay/RecordMetaData.java +@@ -0,0 +1,23 @@ ++package top.leavesmc.leaves.replay; ++ ++import java.util.HashSet; ++import java.util.Set; ++import java.util.UUID; ++ ++public class RecordMetaData { ++ ++ public static final int CURRENT_FILE_FORMAT_VERSION = 14; ++ ++ public boolean singleplayer = false; ++ public String serverName = "Leaves"; ++ public int duration = 0; ++ public long date; ++ public String mcversion; ++ public String fileFormat = "MCPR"; ++ public int fileFormatVersion; ++ public int protocol; ++ public String generator; ++ public int selfId = -1; ++ ++ public Set players = new HashSet<>(); ++} +diff --git a/src/main/java/top/leavesmc/leaves/replay/Recorder.java b/src/main/java/top/leavesmc/leaves/replay/Recorder.java +new file mode 100644 +index 0000000000000000000000000000000000000000..7626e7b1171fcf829673a5fb3c07b3d9e5de48bf +--- /dev/null ++++ b/src/main/java/top/leavesmc/leaves/replay/Recorder.java +@@ -0,0 +1,264 @@ ++package top.leavesmc.leaves.replay; ++ ++import io.netty.channel.local.LocalChannel; ++import net.minecraft.SharedConstants; ++import net.minecraft.core.LayeredRegistryAccess; ++import net.minecraft.core.RegistryAccess; ++import net.minecraft.core.RegistrySynchronization; ++import net.minecraft.network.Connection; ++import net.minecraft.network.ConnectionProtocol; ++import net.minecraft.network.PacketSendListener; ++import net.minecraft.network.protocol.Packet; ++import net.minecraft.network.protocol.PacketFlow; ++import net.minecraft.network.protocol.common.ClientboundCustomPayloadPacket; ++import net.minecraft.network.protocol.common.ClientboundDisconnectPacket; ++import net.minecraft.network.protocol.common.ClientboundUpdateTagsPacket; ++import net.minecraft.network.protocol.common.custom.BrandPayload; ++import net.minecraft.network.protocol.configuration.ClientboundFinishConfigurationPacket; ++import net.minecraft.network.protocol.configuration.ClientboundRegistryDataPacket; ++import net.minecraft.network.protocol.configuration.ClientboundUpdateEnabledFeaturesPacket; ++import net.minecraft.network.protocol.game.ClientboundAddEntityPacket; ++import net.minecraft.network.protocol.game.ClientboundBundlePacket; ++import net.minecraft.network.protocol.game.ClientboundGameEventPacket; ++import net.minecraft.network.protocol.game.ClientboundPlayerChatPacket; ++import net.minecraft.network.protocol.game.ClientboundSetTimePacket; ++import net.minecraft.network.protocol.game.ClientboundSystemChatPacket; ++import net.minecraft.network.protocol.login.ClientboundGameProfilePacket; ++import net.minecraft.server.MinecraftServer; ++import net.minecraft.server.RegistryLayer; ++import net.minecraft.tags.TagNetworkSerialization; ++import net.minecraft.world.entity.EntityType; ++import net.minecraft.world.flag.FeatureFlags; ++import org.jetbrains.annotations.NotNull; ++import org.jetbrains.annotations.Nullable; ++import top.leavesmc.leaves.LeavesLogger; ++ ++import java.io.File; ++import java.io.IOException; ++import java.util.concurrent.CompletableFuture; ++import java.util.concurrent.CompletionException; ++import java.util.concurrent.ExecutorService; ++import java.util.concurrent.Executors; ++import java.util.concurrent.TimeUnit; ++ ++public class Recorder extends Connection { ++ ++ private static final LeavesLogger LOGGER = LeavesLogger.LOGGER; ++ ++ private final ReplayFile replayFile; ++ private final ServerPhotographer photographer; ++ private final RecorderOption recorderOption; ++ private final RecordMetaData metaData; ++ ++ private final ExecutorService saveService = Executors.newSingleThreadExecutor(); ++ ++ private boolean stopped = false; ++ private boolean paused = false; ++ private boolean resumeOnNextPacket = true; ++ ++ private long startTime; ++ private long lastPacket; ++ private long timeShift = 0; ++ ++ private boolean isSaved; ++ private boolean isSaving; ++ private ConnectionProtocol state = ConnectionProtocol.LOGIN; ++ ++ public Recorder(ServerPhotographer photographer, RecorderOption recorderOption, File replayFile) throws IOException { ++ super(PacketFlow.CLIENTBOUND); ++ ++ this.photographer = photographer; ++ this.recorderOption = recorderOption; ++ this.metaData = new RecordMetaData(); ++ this.replayFile = new ReplayFile(replayFile); ++ this.channel = new LocalChannel(); ++ } ++ ++ public void start() { ++ startTime = System.currentTimeMillis(); ++ ++ metaData.singleplayer = false; ++ metaData.serverName = recorderOption.serverName; ++ metaData.generator = "leaves"; ++ metaData.date = startTime; ++ metaData.mcversion = SharedConstants.getCurrentVersion().getName(); ++ ++ // TODO start event ++ savePacket(new ClientboundGameProfilePacket(photographer.getGameProfile()), ConnectionProtocol.LOGIN); ++ startConfiguration(); ++ ++ if (recorderOption.forceWeather != null) { ++ setWeather(recorderOption.forceWeather); ++ } ++ } ++ ++ public void startConfiguration() { ++ state = ConnectionProtocol.CONFIGURATION; ++ MinecraftServer server = MinecraftServer.getServer(); ++ savePacket(new ClientboundCustomPayloadPacket(new BrandPayload(server.getServerModName())), ConnectionProtocol.CONFIGURATION); ++ LayeredRegistryAccess layeredregistryaccess = server.registries(); ++ ++ savePacket(new ClientboundUpdateEnabledFeaturesPacket(FeatureFlags.REGISTRY.toNames(server.getWorldData().enabledFeatures())), ConnectionProtocol.CONFIGURATION); ++ savePacket(new ClientboundRegistryDataPacket((new RegistryAccess.ImmutableRegistryAccess(RegistrySynchronization.networkedRegistries(layeredregistryaccess))).freeze()), ConnectionProtocol.CONFIGURATION); ++ savePacket(new ClientboundUpdateTagsPacket(TagNetworkSerialization.serializeTagsToNetwork(layeredregistryaccess)), ConnectionProtocol.CONFIGURATION); ++ savePacket(new ClientboundFinishConfigurationPacket(), ConnectionProtocol.CONFIGURATION); ++ state = ConnectionProtocol.PLAY; ++ } ++ ++ @Override ++ public void flushChannel() { ++ } ++ ++ public void stop() { ++ stopped = true; ++ } ++ ++ public void pauseRecording() { ++ resumeOnNextPacket = false; ++ paused = true; ++ } ++ ++ public void resumeRecording() { ++ resumeOnNextPacket = true; ++ } ++ ++ public void setWeather(RecorderOption.RecordWeather weather) { ++ weather.getPackets().forEach(this::savePacket); ++ } ++ ++ public long getRecordedTime() { ++ final long base = System.currentTimeMillis() - startTime; ++ return base - timeShift; ++ } ++ ++ private synchronized long getCurrentTimeAndUpdate() { ++ long now = getRecordedTime(); ++ if (paused) { ++ if (resumeOnNextPacket) { ++ paused = false; ++ } ++ timeShift += now - lastPacket; ++ return lastPacket; ++ } ++ return lastPacket = now; ++ } ++ ++ @Override ++ public boolean isConnected() { ++ return true; ++ } ++ ++ @Override ++ public void send(@NotNull Packet packet, @Nullable PacketSendListener callbacks, boolean flush) { ++ if (!stopped) { ++ if (packet instanceof ClientboundBundlePacket packet1) { ++ packet1.subPackets().forEach(subPacket -> { ++ send(subPacket, null); ++ }); ++ } ++ ++ if (packet instanceof ClientboundAddEntityPacket packet1) { ++ if (packet1.getType() == EntityType.PLAYER) { ++ metaData.players.add(packet1.getUUID()); ++ saveMetadata(); ++ } ++ } ++ ++ if (packet instanceof ClientboundDisconnectPacket) { ++ return; ++ } ++ ++ if (recorderOption.forceDayTime != -1 && packet instanceof ClientboundSetTimePacket packet1) { ++ packet = new ClientboundSetTimePacket(packet1.getDayTime(), recorderOption.forceDayTime, false); ++ } ++ ++ if (recorderOption.forceWeather != null && packet instanceof ClientboundGameEventPacket packet1) { ++ ClientboundGameEventPacket.Type type = packet1.getEvent(); ++ if (type == ClientboundGameEventPacket.START_RAINING || type == ClientboundGameEventPacket.STOP_RAINING || type == ClientboundGameEventPacket.RAIN_LEVEL_CHANGE || type == ClientboundGameEventPacket.THUNDER_LEVEL_CHANGE) { ++ return; ++ } ++ } ++ ++ if (recorderOption.ignoreChat && (packet instanceof ClientboundSystemChatPacket || packet instanceof ClientboundPlayerChatPacket)) { ++ return; ++ } ++ ++ savePacket(packet); ++ } ++ } ++ ++ private void saveMetadata() { ++ saveService.submit(() -> { ++ try { ++ replayFile.saveMetaData(metaData); ++ } catch (IOException e) { ++ e.printStackTrace(); ++ } ++ }); ++ } ++ ++ private void savePacket(Packet packet) { ++ this.savePacket(packet, state); ++ } ++ ++ private void savePacket(Packet packet, final ConnectionProtocol protocol) { ++ try { ++ final long timestamp = getCurrentTimeAndUpdate(); ++ saveService.submit(() -> { ++ try { ++ replayFile.savePacket(timestamp, packet, protocol); ++ } catch (Exception e) { ++ LOGGER.severe("Error saving packet"); ++ e.printStackTrace(); ++ } ++ }); ++ } catch (Exception e) { ++ LOGGER.severe("Error saving packet"); ++ e.printStackTrace(); ++ } ++ } ++ ++ public boolean isSaved() { ++ return isSaved; ++ } ++ ++ public CompletableFuture saveRecording(File dest, boolean save) { ++ isSaved = true; ++ if (!isSaving) { ++ isSaving = true; ++ metaData.duration = (int) lastPacket; ++ return CompletableFuture.runAsync(() -> { ++ saveMetadata(); ++ saveService.shutdown(); ++ boolean interrupted = false; ++ try { ++ saveService.awaitTermination(10, TimeUnit.SECONDS); ++ } catch (InterruptedException e) { ++ interrupted = true; ++ } ++ try { ++ if (save) { ++ replayFile.closeAndSave(dest); ++ } else { ++ replayFile.closeNotSave(); ++ } ++ } catch (IOException e) { ++ e.printStackTrace(); ++ throw new CompletionException(e); ++ } finally { ++ if (interrupted) { ++ Thread.currentThread().interrupt(); ++ } ++ } ++ }, runnable -> { ++ final Thread thread = new Thread(runnable, "Recording file save thread"); ++ thread.start(); ++ }); ++ } else { ++ LOGGER.warning("saveRecording() called twice"); ++ return CompletableFuture.supplyAsync(() -> { ++ throw new IllegalStateException("saveRecording() called twice"); ++ }); ++ } ++ } ++} +diff --git a/src/main/java/top/leavesmc/leaves/replay/RecorderOption.java b/src/main/java/top/leavesmc/leaves/replay/RecorderOption.java +new file mode 100644 +index 0000000000000000000000000000000000000000..06e7166336d621e1a8edb4a2ad88e2cb8a52abb1 +--- /dev/null ++++ b/src/main/java/top/leavesmc/leaves/replay/RecorderOption.java +@@ -0,0 +1,57 @@ ++package top.leavesmc.leaves.replay; ++ ++import net.minecraft.network.protocol.Packet; ++import net.minecraft.network.protocol.game.ClientboundGameEventPacket; ++import org.jetbrains.annotations.Contract; ++import org.jetbrains.annotations.NotNull; ++ ++import java.util.List; ++ ++public class RecorderOption { ++ ++ public int recordDistance = -1; ++ public String serverName = "Leaves"; ++ public RecordWeather forceWeather = null; ++ public int forceDayTime = -1; ++ public boolean ignoreChat = false; ++ public boolean ignoreItem = false; ++ ++ @NotNull ++ @Contract(" -> new") ++ public static RecorderOption createDefaultOption() { ++ return new RecorderOption(); ++ } ++ ++ @NotNull ++ public static RecorderOption createFromBukkit(@NotNull BukkitRecorderOption bukkitRecorderOption) { ++ RecorderOption recorderOption = new RecorderOption(); ++ // recorderOption.recordDistance = bukkitRecorderOption.recordDistance; ++ // recorderOption.ignoreItem = bukkitRecorderOption.ignoreItem; ++ recorderOption.serverName = bukkitRecorderOption.serverName; ++ recorderOption.ignoreChat = bukkitRecorderOption.ignoreChat; ++ recorderOption.forceDayTime = bukkitRecorderOption.forceDayTime; ++ recorderOption.forceWeather = switch (bukkitRecorderOption.forceWeather) { ++ case RAIN -> RecordWeather.RAIN; ++ case CLEAR -> RecordWeather.CLEAR; ++ case THUNDER -> RecordWeather.THUNDER; ++ case NULL -> null; ++ }; ++ return recorderOption; ++ } ++ ++ public enum RecordWeather { ++ CLEAR(new ClientboundGameEventPacket(ClientboundGameEventPacket.STOP_RAINING, 0), new ClientboundGameEventPacket(ClientboundGameEventPacket.RAIN_LEVEL_CHANGE, 0), new ClientboundGameEventPacket(ClientboundGameEventPacket.THUNDER_LEVEL_CHANGE, 0)), ++ RAIN(new ClientboundGameEventPacket(ClientboundGameEventPacket.START_RAINING, 0), new ClientboundGameEventPacket(ClientboundGameEventPacket.RAIN_LEVEL_CHANGE, 1), new ClientboundGameEventPacket(ClientboundGameEventPacket.THUNDER_LEVEL_CHANGE, 0)), ++ THUNDER(new ClientboundGameEventPacket(ClientboundGameEventPacket.START_RAINING, 0), new ClientboundGameEventPacket(ClientboundGameEventPacket.RAIN_LEVEL_CHANGE, 1), new ClientboundGameEventPacket(ClientboundGameEventPacket.THUNDER_LEVEL_CHANGE, 1)); ++ ++ private final List> packets; ++ ++ private RecordWeather(Packet... packets) { ++ this.packets = List.of(packets); ++ } ++ ++ public List> getPackets() { ++ return packets; ++ } ++ } ++} +diff --git a/src/main/java/top/leavesmc/leaves/replay/ReplayFile.java b/src/main/java/top/leavesmc/leaves/replay/ReplayFile.java +new file mode 100644 +index 0000000000000000000000000000000000000000..8cb0f7e2c9f6b7455541c5e06acca0e1ceb60a11 +--- /dev/null ++++ b/src/main/java/top/leavesmc/leaves/replay/ReplayFile.java +@@ -0,0 +1,178 @@ ++package top.leavesmc.leaves.replay; ++ ++import com.google.gson.Gson; ++import com.google.gson.GsonBuilder; ++import io.netty.buffer.ByteBuf; ++import io.netty.buffer.Unpooled; ++import net.minecraft.SharedConstants; ++import net.minecraft.network.ConnectionProtocol; ++import net.minecraft.network.FriendlyByteBuf; ++import net.minecraft.network.protocol.Packet; ++import net.minecraft.network.protocol.PacketFlow; ++import org.jetbrains.annotations.NotNull; ++import top.leavesmc.leaves.util.UUIDSerializer; ++ ++import java.io.BufferedOutputStream; ++import java.io.DataOutputStream; ++import java.io.File; ++import java.io.FileInputStream; ++import java.io.FileOutputStream; ++import java.io.IOException; ++import java.io.InputStream; ++import java.io.OutputStream; ++import java.io.OutputStreamWriter; ++import java.io.Writer; ++import java.nio.charset.StandardCharsets; ++import java.nio.file.Files; ++import java.util.List; ++import java.util.UUID; ++import java.util.zip.CRC32; ++import java.util.zip.ZipEntry; ++import java.util.zip.ZipOutputStream; ++ ++public class ReplayFile { ++ ++ private static final String RECORDING_FILE = "recording.tmcpr"; ++ private static final String RECORDING_FILE_CRC32 = "recording.tmcpr.crc32"; ++ private static final String MARKER_FILE = "markers.json"; ++ private static final String META_FILE = "metaData.json"; ++ ++ private static final Gson MARKER_GSON = new GsonBuilder().registerTypeAdapter(ReplayMarker.class, new ReplayMarker.Serializer()).create(); ++ private static final Gson META_GSON = new GsonBuilder().registerTypeAdapter(UUID.class, new UUIDSerializer()).create(); ++ ++ private final File tmpDir; ++ private final DataOutputStream packetStream; ++ private final CRC32 crc32 = new CRC32(); ++ ++ private final File markerFile; ++ private final File metaFile; ++ ++ public ReplayFile(@NotNull File name) throws IOException { ++ this.tmpDir = new File(name.getParentFile(), name.getName() + ".tmp"); ++ if (tmpDir.exists()) { ++ if (!ReplayFile.deleteDir(tmpDir)) { ++ throw new IOException("Recording file " + name + " already exists!"); ++ } ++ } ++ ++ if (!tmpDir.mkdirs()) { ++ throw new IOException("Failed to create temp directory for recording " + tmpDir); ++ } ++ ++ File packetFile = new File(tmpDir, RECORDING_FILE); ++ metaFile = new File(tmpDir, META_FILE); ++ markerFile = new File(tmpDir, MARKER_FILE); ++ ++ packetStream = new DataOutputStream(new DigestOutputStream(new BufferedOutputStream(new FileOutputStream(packetFile)), crc32)); ++ } ++ ++ private byte @NotNull [] getPacketBytes(Packet packet, ConnectionProtocol state) { ++ int packetID = state.codec(PacketFlow.CLIENTBOUND).packetId(packet); ++ ByteBuf buf = Unpooled.buffer(); ++ FriendlyByteBuf packetBuf = new FriendlyByteBuf(buf); ++ packetBuf.writeVarInt(packetID); ++ packet.write(packetBuf); ++ ++ buf.readerIndex(0); ++ byte[] ret = new byte[buf.readableBytes()]; ++ buf.readBytes(ret); ++ buf.release(); ++ return ret; ++ } ++ ++ public void saveMarkers(List markers) throws IOException { ++ try (Writer writer = new OutputStreamWriter(new FileOutputStream(markerFile), StandardCharsets.UTF_8)) { ++ writer.write(MARKER_GSON.toJson(markers)); ++ } ++ } ++ ++ public void saveMetaData(@NotNull RecordMetaData data) throws IOException { ++ data.fileFormat = "MCPR"; ++ data.fileFormatVersion = RecordMetaData.CURRENT_FILE_FORMAT_VERSION; ++ data.protocol = SharedConstants.getCurrentVersion().getProtocolVersion(); ++ ++ try (Writer writer = new OutputStreamWriter(new FileOutputStream(metaFile), StandardCharsets.UTF_8)) { ++ writer.write(META_GSON.toJson(data)); ++ } ++ } ++ ++ public void savePacket(long timestamp, Packet packet, ConnectionProtocol protocol) throws Exception { ++ byte[] data = getPacketBytes(packet, protocol); ++ packetStream.writeInt((int) timestamp); ++ packetStream.writeInt(data.length); ++ packetStream.write(data); ++ } ++ ++ public synchronized void closeAndSave(File file) throws IOException { ++ packetStream.close(); ++ ++ String[] files = tmpDir.list(); ++ if (files == null) { ++ return; ++ } ++ ++ try (ZipOutputStream os = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(file)))) { ++ for (String fileName : files) { ++ os.putNextEntry(new ZipEntry(fileName)); ++ File f = new File(tmpDir, fileName); ++ copy(new FileInputStream(f), os); ++ } ++ ++ os.putNextEntry(new ZipEntry(RECORDING_FILE_CRC32)); ++ Writer writer = new OutputStreamWriter(os); ++ writer.write(Long.toString(crc32.getValue())); ++ writer.flush(); ++ } ++ ++ for (String fileName : files) { ++ File f = new File(tmpDir, fileName); ++ Files.delete(f.toPath()); ++ } ++ Files.delete(tmpDir.toPath()); ++ } ++ ++ public synchronized void closeNotSave() throws IOException { ++ packetStream.close(); ++ ++ String[] files = tmpDir.list(); ++ if (files == null) { ++ return; ++ } ++ ++ for (String fileName : files) { ++ File f = new File(tmpDir, fileName); ++ Files.delete(f.toPath()); ++ } ++ Files.delete(tmpDir.toPath()); ++ } ++ ++ private void copy(@NotNull InputStream in, OutputStream out) throws IOException { ++ byte[] buffer = new byte[8192]; ++ int len; ++ while ((len = in.read(buffer)) > -1) { ++ out.write(buffer, 0, len); ++ } ++ in.close(); ++ } ++ ++ private static boolean deleteDir(File dir) { ++ if (dir == null || !dir.exists()) { ++ return false; ++ } ++ ++ File[] files = dir.listFiles(); ++ if (files != null) { ++ for (File file : files) { ++ if (file.isDirectory()) { ++ deleteDir(file); ++ } else { ++ if (!file.delete()) { ++ return false; ++ } ++ } ++ } ++ } ++ ++ return dir.delete(); ++ } ++} +diff --git a/src/main/java/top/leavesmc/leaves/replay/ReplayMarker.java b/src/main/java/top/leavesmc/leaves/replay/ReplayMarker.java +new file mode 100644 +index 0000000000000000000000000000000000000000..852f2098d93d4437fe79af06e454d8494b6decf1 +--- /dev/null ++++ b/src/main/java/top/leavesmc/leaves/replay/ReplayMarker.java +@@ -0,0 +1,43 @@ ++package top.leavesmc.leaves.replay; ++ ++import com.google.gson.JsonElement; ++import com.google.gson.JsonObject; ++import com.google.gson.JsonPrimitive; ++import com.google.gson.JsonSerializationContext; ++import com.google.gson.JsonSerializer; ++ ++import java.lang.reflect.Type; ++ ++public class ReplayMarker { ++ ++ public int time; ++ public String name; ++ public double x = 0; ++ public double y = 0; ++ public double z = 0; ++ public float phi = 0; ++ public float theta = 0; ++ public float varphi = 0; ++ ++ public static class Serializer implements JsonSerializer { ++ @Override ++ public JsonElement serialize(ReplayMarker src, Type typeOfSrc, JsonSerializationContext context) { ++ JsonObject ret = new JsonObject(); ++ JsonObject value = new JsonObject(); ++ JsonObject position = new JsonObject(); ++ ret.add("realTimestamp", new JsonPrimitive(src.time)); ++ ret.add("value", value); ++ ++ value.add("name", new JsonPrimitive(src.name)); ++ value.add("position", position); ++ ++ position.add("x", new JsonPrimitive(src.x)); ++ position.add("y", new JsonPrimitive(src.y)); ++ position.add("z", new JsonPrimitive(src.z)); ++ position.add("yaw", new JsonPrimitive(src.phi)); ++ position.add("pitch", new JsonPrimitive(src.theta)); ++ position.add("roll", new JsonPrimitive(src.varphi)); ++ return ret; ++ } ++ } ++} +diff --git a/src/main/java/top/leavesmc/leaves/replay/ServerPhotographer.java b/src/main/java/top/leavesmc/leaves/replay/ServerPhotographer.java +new file mode 100644 +index 0000000000000000000000000000000000000000..7818db1ca51fd61c96a74bec3d31a0c1d5efe4cc +--- /dev/null ++++ b/src/main/java/top/leavesmc/leaves/replay/ServerPhotographer.java +@@ -0,0 +1,216 @@ ++package top.leavesmc.leaves.replay; ++ ++import com.mojang.authlib.GameProfile; ++import net.minecraft.server.MinecraftServer; ++import net.minecraft.server.level.ClientInformation; ++import net.minecraft.server.level.ServerLevel; ++import net.minecraft.server.level.ServerPlayer; ++import net.minecraft.stats.ServerStatsCounter; ++import net.minecraft.world.damagesource.DamageSource; ++import net.minecraft.world.level.GameType; ++import net.minecraft.world.phys.Vec3; ++import org.bukkit.Location; ++import org.bukkit.craftbukkit.CraftWorld; ++import org.jetbrains.annotations.NotNull; ++import top.leavesmc.leaves.LeavesLogger; ++import top.leavesmc.leaves.bot.BotStatsCounter; ++import top.leavesmc.leaves.entity.CraftPhotographer; ++import top.leavesmc.leaves.entity.Photographer; ++ ++import java.io.File; ++import java.io.IOException; ++import java.util.List; ++import java.util.UUID; ++import java.util.concurrent.CompletableFuture; ++import java.util.concurrent.CopyOnWriteArrayList; ++ ++import static top.leavesmc.leaves.bot.ServerBot.isCreateLegal; ++ ++public class ServerPhotographer extends ServerPlayer { ++ ++ private static final List photographers = new CopyOnWriteArrayList<>(); ++ ++ public PhotographerCreateState createState; ++ private ServerPlayer followPlayer; ++ private Recorder recorder; ++ private File saveFile; ++ private Vec3 lastPos; ++ ++ private final ServerStatsCounter stats; ++ ++ private ServerPhotographer(MinecraftServer server, ServerLevel world, GameProfile profile) { ++ super(server, world, profile, ClientInformation.createDefault()); ++ this.gameMode = new ServerPhotographerGameMode(this); ++ this.followPlayer = null; ++ this.stats = new BotStatsCounter(server); ++ this.lastPos = this.position(); ++ } ++ ++ public static ServerPhotographer createPhotographer(@NotNull PhotographerCreateState state) throws IOException { ++ if (!isCreateLegal(state.id)) { ++ return null; ++ } ++ ++ MinecraftServer server = MinecraftServer.getServer(); ++ ++ ServerLevel world = ((CraftWorld) state.loc.getWorld()).getHandle(); ++ GameProfile profile = new GameProfile(UUID.randomUUID(), state.id); ++ ++ ServerPhotographer photographer = new ServerPhotographer(server, world, profile); ++ photographer.recorder = new Recorder(photographer, state.option, new File("replay", state.id)); ++ photographer.saveFile = new File("replay", state.id + ".mcpr"); ++ photographer.createState = state; ++ ++ photographer.recorder.start(); ++ MinecraftServer.getServer().getPlayerList().placeNewPhotographer(photographer.recorder, photographer, world, state.loc); ++ photographer.serverLevel().chunkSource.move(photographer); ++ photographer.setInvisible(true); ++ photographers.add(photographer); ++ ++ LeavesLogger.LOGGER.info("Photographer " + state.id + " created"); ++ ++ // TODO record distance ++ ++ return photographer; ++ } ++ ++ @Override ++ public void tick() { ++ super.tick(); ++ super.doTick(); ++ ++ if (this.server.getTickCount() % 10 == 0) { ++ connection.resetPosition(); ++ this.serverLevel().chunkSource.move(this); ++ } ++ ++ if (this.followPlayer != null) { ++ if (this.getCamera() == this || this.getCamera().level() != this.level()) { ++ this.getBukkitPlayer().teleport(this.getCamera().getBukkitEntity().getLocation()); ++ this.setCamera(followPlayer); ++ } ++ if (lastPos.distanceToSqr(this.position()) > 1024D) { ++ this.getBukkitPlayer().teleport(this.getCamera().getBukkitEntity().getLocation()); ++ } ++ } ++ ++ lastPos = this.position(); ++ } ++ ++ @Override ++ public void die(@NotNull DamageSource damageSource) { ++ super.die(damageSource); ++ remove(true); ++ } ++ ++ @Override ++ public boolean isInvulnerableTo(@NotNull DamageSource damageSource) { ++ return true; ++ } ++ ++ @Override ++ public boolean hurt(@NotNull DamageSource source, float amount) { ++ return false; ++ } ++ ++ @Override ++ public void setHealth(float health) { ++ } ++ ++ @NotNull ++ @Override ++ public ServerStatsCounter getStats() { ++ return stats; ++ } ++ ++ public void remove(boolean async) { ++ this.remove(async, true); ++ } ++ ++ public void remove(boolean async, boolean save) { ++ super.remove(RemovalReason.KILLED); ++ photographers.remove(this); ++ this.recorder.stop(); ++ this.server.getPlayerList().removePhotographer(this); ++ ++ LeavesLogger.LOGGER.info("Photographer " + createState.id + " removed"); ++ ++ if (save && !recorder.isSaved()) { ++ CompletableFuture future = recorder.saveRecording(saveFile, save); ++ if (!async) { ++ future.join(); ++ } ++ } ++ } ++ ++ public void setFollowPlayer(ServerPlayer followPlayer) { ++ this.setCamera(followPlayer); ++ this.followPlayer = followPlayer; ++ } ++ ++ public void setSaveFile(File saveFile) { ++ this.saveFile = saveFile; ++ } ++ ++ public void pauseRecording() { ++ this.recorder.pauseRecording(); ++ } ++ ++ public void resumeRecording() { ++ this.recorder.resumeRecording(); ++ } ++ ++ public static ServerPhotographer getPhotographer(String id) { ++ for (ServerPhotographer photographer : photographers) { ++ if (photographer.createState.id.equals(id)) { ++ return photographer; ++ } ++ } ++ return null; ++ } ++ ++ public static ServerPhotographer getPhotographer(UUID uuid) { ++ for (ServerPhotographer photographer : photographers) { ++ if (photographer.getUUID().equals(uuid)) { ++ return photographer; ++ } ++ } ++ return null; ++ } ++ ++ public static List getPhotographers() { ++ return photographers; ++ } ++ ++ public Photographer getBukkitPlayer() { ++ return getBukkitEntity(); ++ } ++ ++ @Override ++ @NotNull ++ public CraftPhotographer getBukkitEntity() { ++ return (CraftPhotographer) super.getBukkitEntity(); ++ } ++ ++ public static class PhotographerCreateState { ++ ++ public RecorderOption option; ++ public Location loc; ++ public final String id; ++ ++ public PhotographerCreateState(Location loc, String id, RecorderOption option) { ++ this.loc = loc; ++ this.id = id; ++ this.option = option; ++ } ++ ++ public ServerPhotographer createSync() { ++ try { ++ return createPhotographer(this); ++ } catch (IOException e) { ++ e.printStackTrace(); ++ } ++ return null; ++ } ++ } ++} +diff --git a/src/main/java/top/leavesmc/leaves/replay/ServerPhotographerGameMode.java b/src/main/java/top/leavesmc/leaves/replay/ServerPhotographerGameMode.java +new file mode 100644 +index 0000000000000000000000000000000000000000..8a1cd9b1fe9823d5a9f578ced54ec25e75d44208 +--- /dev/null ++++ b/src/main/java/top/leavesmc/leaves/replay/ServerPhotographerGameMode.java +@@ -0,0 +1,35 @@ ++package top.leavesmc.leaves.replay; ++ ++import net.kyori.adventure.text.Component; ++import net.minecraft.server.level.ServerPlayerGameMode; ++import net.minecraft.world.level.GameType; ++import org.bukkit.event.player.PlayerGameModeChangeEvent; ++import org.jetbrains.annotations.NotNull; ++import org.jetbrains.annotations.Nullable; ++ ++public class ServerPhotographerGameMode extends ServerPlayerGameMode { ++ ++ public ServerPhotographerGameMode(ServerPhotographer photographer) { ++ super(photographer); ++ super.setGameModeForPlayer(GameType.SPECTATOR, null); ++ } ++ ++ @Override ++ public boolean changeGameModeForPlayer(@NotNull GameType gameMode) { ++ return false; ++ } ++ ++ @Nullable ++ @Override ++ public PlayerGameModeChangeEvent changeGameModeForPlayer(@NotNull GameType gameMode, PlayerGameModeChangeEvent.@NotNull Cause cause, @Nullable Component cancelMessage) { ++ return null; ++ } ++ ++ @Override ++ protected void setGameModeForPlayer(@NotNull GameType gameMode, @Nullable GameType previousGameMode) { ++ } ++ ++ @Override ++ public void tick() { ++ } ++} +diff --git a/src/main/java/top/leavesmc/leaves/util/UUIDSerializer.java b/src/main/java/top/leavesmc/leaves/util/UUIDSerializer.java +new file mode 100644 +index 0000000000000000000000000000000000000000..1329a725a2bd03d3ef6d7131d8bc77f20bf2e566 +--- /dev/null ++++ b/src/main/java/top/leavesmc/leaves/util/UUIDSerializer.java +@@ -0,0 +1,17 @@ ++package top.leavesmc.leaves.util; ++ ++import com.google.gson.JsonElement; ++import com.google.gson.JsonPrimitive; ++import com.google.gson.JsonSerializationContext; ++import com.google.gson.JsonSerializer; ++import org.jetbrains.annotations.NotNull; ++ ++import java.lang.reflect.Type; ++import java.util.UUID; ++ ++public class UUIDSerializer implements JsonSerializer { ++ @Override ++ public JsonElement serialize(@NotNull UUID src, Type typeOfSrc, JsonSerializationContext context) { ++ return new JsonPrimitive(src.toString()); ++ } ++} diff --git a/patches/unapplied/server/0115-Servux-Protocol.patch b/patches/unapplied/server/0115-Servux-Protocol.patch new file mode 100644 index 00000000..c8c40f52 --- /dev/null +++ b/patches/unapplied/server/0115-Servux-Protocol.patch @@ -0,0 +1,199 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: violetc <58360096+s-yh-china@users.noreply.github.com> +Date: Wed, 13 Sep 2023 19:31:20 +0800 +Subject: [PATCH] Servux Protocol + + +diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java +index 46230b09fc176665ba1d29ab3233c3b956a07710..abef121b9882c17a200e9f0186ee12634b8f8dfc 100644 +--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java ++++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java +@@ -934,6 +934,7 @@ public class LevelChunk extends ChunkAccess { + // Leaves start - bbor + if (loaded) { + top.leavesmc.leaves.protocol.BBORProtocol.onChunkLoaded(this); ++ top.leavesmc.leaves.protocol.ServuxProtocol.onChunkLoaded(this); // and servux + } + // Leaves end - bbor + } +diff --git a/src/main/java/top/leavesmc/leaves/protocol/ServuxProtocol.java b/src/main/java/top/leavesmc/leaves/protocol/ServuxProtocol.java +new file mode 100644 +index 0000000000000000000000000000000000000000..f6fa9368f66de0f23d5387b38c0cff35658d3d59 +--- /dev/null ++++ b/src/main/java/top/leavesmc/leaves/protocol/ServuxProtocol.java +@@ -0,0 +1,175 @@ ++package top.leavesmc.leaves.protocol; ++ ++import io.netty.buffer.Unpooled; ++import net.minecraft.core.Registry; ++import net.minecraft.core.registries.Registries; ++import net.minecraft.nbt.CompoundTag; ++import net.minecraft.nbt.ListTag; ++import net.minecraft.network.FriendlyByteBuf; ++import net.minecraft.resources.ResourceLocation; ++import net.minecraft.server.level.ServerLevel; ++import net.minecraft.server.level.ServerPlayer; ++import net.minecraft.world.level.chunk.LevelChunk; ++import net.minecraft.world.level.levelgen.structure.BoundingBox; ++import net.minecraft.world.level.levelgen.structure.Structure; ++import net.minecraft.world.level.levelgen.structure.StructureStart; ++import net.minecraft.world.level.levelgen.structure.pieces.StructurePieceSerializationContext; ++import org.jetbrains.annotations.Contract; ++import org.jetbrains.annotations.NotNull; ++import top.leavesmc.leaves.LeavesConfig; ++import top.leavesmc.leaves.protocol.core.LeavesProtocol; ++import top.leavesmc.leaves.protocol.core.ProtocolHandler; ++import top.leavesmc.leaves.protocol.core.ProtocolUtils; ++ ++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.concurrent.ConcurrentHashMap; ++ ++@LeavesProtocol(namespace = "servux") ++public class ServuxProtocol { ++ ++ public static final String PROTOCOL_ID = "servux"; ++ ++ public static final int PROTOCOL_VERSION = 1; ++ public static final int PACKET_METADATA = 1; ++ public static final int PACKET_STRUCTURE_DATA = 2; ++ ++ public static final ResourceLocation CHANNEL = id("structures"); ++ ++ private static final Map players = new ConcurrentHashMap<>(); ++ private static final Map> playerBoundingBoxesCache = new HashMap<>(); ++ private static final Map> dimensionCache = new ConcurrentHashMap<>(); ++ ++ @Contract("_ -> new") ++ public static @NotNull ResourceLocation id(String path) { ++ return new ResourceLocation(PROTOCOL_ID, path); ++ } ++ ++ @ProtocolHandler.MinecraftRegister(channelId = "structures") ++ public static void onPlayerSubscribed(@NotNull ServerPlayer player) { ++ if (LeavesConfig.servuxProtocol) { ++ players.put(player.getId(), player); ++ ++ CompoundTag tag = new CompoundTag(); ++ tag.putInt("version", PROTOCOL_VERSION); ++ tag.putString("id", CHANNEL.toString()); ++ tag.putInt("timeout", Integer.MAX_VALUE - 200); ++ sendNBTPacket(player, PACKET_METADATA, tag); ++ } ++ } ++ ++ @ProtocolHandler.PlayerLeave ++ public static void onPlayerLoggedOut(@NotNull ServerPlayer player) { ++ if (LeavesConfig.servuxProtocol) { ++ players.remove(player.getId()); ++ playerBoundingBoxesCache.remove(player.getId()); ++ } ++ } ++ ++ @ProtocolHandler.Ticker ++ public static void tick() { ++ if (LeavesConfig.servuxProtocol) { ++ for (var playerEntry : players.entrySet()) { ++ sendBoundingToPlayer(playerEntry.getKey(), playerEntry.getValue()); ++ } ++ } ++ } ++ ++ public static void onChunkLoaded(@NotNull LevelChunk chunk) { ++ if (LeavesConfig.servuxProtocol) { ++ List structures = new ArrayList<>(); ++ final Registry structureFeatureRegistry = chunk.getLevel().registryAccess().registryOrThrow(Registries.STRUCTURE); ++ for (var es : chunk.getAllStarts().entrySet()) { ++ final var optional = structureFeatureRegistry.getResourceKey(es.getKey()); ++ optional.ifPresent(key -> structures.add(es.getValue())); ++ } ++ ++ if (!structures.isEmpty()) { ++ onStructuresLoaded((ServerLevel) chunk.getLevel(), structures); ++ } ++ } ++ } ++ ++ public static void onStructuresLoaded(@NotNull ServerLevel level, @NotNull List structures) { ++ Map cache = getOrCreateCache(level.dimension().location()); ++ for (StructureStart structureStart : structures) { ++ if (structureStart == null) { ++ return; ++ } ++ ++ StructurePieceSerializationContext ctx = StructurePieceSerializationContext.fromLevel(level); ++ ++ BoundingBox boundingBox = structureStart.getBoundingBox(); ++ if (cache.containsKey(boundingBox)) { ++ return; ++ } ++ ++ cache.put(boundingBox, structureStart.createTag(ctx, structureStart.getChunkPos())); ++ } ++ } ++ ++ private static void sendBoundingToPlayer(int id, ServerPlayer player) { ++ Map boundingBoxMap = dimensionCache.get(player.level().dimension().location()); ++ if (boundingBoxMap == null) { ++ return; ++ } ++ ++ Set playerBoundingBoxes = playerBoundingBoxesCache.computeIfAbsent(id, k -> new HashSet<>()); ++ ++ ListTag listTag = new ListTag(); ++ for (BoundingBox key : boundingBoxMap.keySet()) { ++ if (listTag.size() >= 50) { ++ break; ++ } ++ if (playerBoundingBoxes.contains(key)) { ++ continue; ++ } ++ ++ CompoundTag boundingBoxes = boundingBoxMap.get(key); ++ if (boundingBoxes != null) { ++ listTag.add(boundingBoxes); ++ } ++ playerBoundingBoxes.add(key); ++ } ++ ++ if (!listTag.isEmpty()) { ++ CompoundTag tag = new CompoundTag(); ++ tag.put("Structures", listTag); ++ sendNBTPacket(player, PACKET_STRUCTURE_DATA, tag); ++ } ++ } ++ ++ private static Map getOrCreateCache(ResourceLocation dimensionId) { ++ return dimensionCache.computeIfAbsent(dimensionId, dt -> new ConcurrentHashMap<>()); ++ } ++ ++ private static final int MAX_TOTAL_PER_PACKET_S2C = 1048576; ++ private static final int MAX_PAYLOAD_PER_PACKET_S2C = MAX_TOTAL_PER_PACKET_S2C - 5; ++ ++ public static void sendNBTPacket(ServerPlayer player, int packetType, CompoundTag data) { ++ FriendlyByteBuf packet = new FriendlyByteBuf(Unpooled.buffer()); ++ packet.writeVarInt(packetType); ++ packet.writeNbt(data); ++ ++ int len = packet.writerIndex(); ++ packet.readerIndex(0); ++ ++ for (int offset = 0; offset < len; offset += MAX_PAYLOAD_PER_PACKET_S2C) { ++ int thisLen = Math.min(len - offset, MAX_PAYLOAD_PER_PACKET_S2C); ++ FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer(thisLen)); ++ ++ if (offset == 0) { ++ buf.writeVarInt(len); ++ } ++ ++ buf.writeBytes(packet, thisLen); ++ ProtocolUtils.sendPayloadPacket(player, CHANNEL, buf1 -> buf1.writeBytes(buf)); ++ } ++ ++ packet.release(); ++ } ++}